js/src/vm/SPSProfiler.h

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2  * vim: set ts=8 sts=4 et sw=4 tw=99:
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #ifndef vm_SPSProfiler_h
     8 #define vm_SPSProfiler_h
    10 #include "mozilla/DebugOnly.h"
    11 #include "mozilla/GuardObjects.h"
    13 #include <stddef.h>
    15 #include "jslock.h"
    16 #include "jsscript.h"
    18 #include "js/ProfilingStack.h"
    20 /*
    21  * SPS Profiler integration with the JS Engine
    22  * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
    23  *
    24  * The SPS profiler (found in tools/profiler) is an implementation of a profiler
    25  * which has the ability to walk the C++ stack as well as use instrumentation to
    26  * gather information. When dealing with JS, however, SPS needs integration
    27  * with the engine because otherwise it is very difficult to figure out what
    28  * javascript is executing.
    29  *
    30  * The current method of integration with SPS is a form of instrumentation:
    31  * every time a JS function is entered, a bit of information is pushed onto a
    32  * stack that SPS owns and maintains. This information is then popped at the end
    33  * of the JS function. SPS informs the JS engine of this stack at runtime, and
    34  * it can by turned on/off dynamically.
    35  *
    36  * The SPS stack has three parameters: a base pointer, a size, and a maximum
    37  * size. The stack is the ProfileEntry stack which will have information written
    38  * to it. The size location is a pointer to an integer which represents the
    39  * current size of the stack (number of valid frames). This size will be
    40  * modified when JS functions are called. The maximum specified is the maximum
    41  * capacity of the ProfileEntry stack.
    42  *
    43  * Throughout execution, the size of the stack recorded in memory may exceed the
    44  * maximum. The JS engine will not write any information past the maximum limit,
    45  * but it will still maintain the size of the stack. SPS code is aware of this
    46  * and iterates the stack accordingly.
    47  *
    48  * There is some information pushed on the SPS stack for every JS function that
    49  * is entered. First is a char* pointer of a description of what function was
    50  * entered. Currently this string is of the form "function (file:line)" if
    51  * there's a function name, or just "file:line" if there's no function name
    52  * available. The other bit of information is the relevant C++ (native) stack
    53  * pointer. This stack pointer is what enables the interleaving of the C++ and
    54  * the JS stack. Finally, throughout execution of the function, some extra
    55  * information may be updated on the ProfileEntry structure.
    56  *
    57  * = Profile Strings
    58  *
    59  * The profile strings' allocations and deallocation must be carefully
    60  * maintained, and ideally at a very low overhead cost. For this reason, the JS
    61  * engine maintains a mapping of all known profile strings. These strings are
    62  * keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
    63  * JSScript* pair. A JSScript will destroy its corresponding profile string when
    64  * the script is finalized.
    65  *
    66  * For this reason, a char* pointer pushed on the SPS stack is valid only while
    67  * it is on the SPS stack. SPS uses sampling to read off information from this
    68  * instrumented stack, and it therefore copies the string byte for byte when a
    69  * JS function is encountered during sampling.
    70  *
    71  * = Native Stack Pointer
    72  *
    73  * The actual value pushed as the native pointer is nullptr for most JS
    74  * functions. The reason for this is that there's actually very little
    75  * correlation between the JS stack and the C++ stack because many JS functions
    76  * all run in the same C++ frame, or can even go backwards in C++ when going
    77  * from the JIT back to the interpreter.
    78  *
    79  * To alleviate this problem, all JS functions push nullptr as their "native
    80  * stack pointer" to indicate that it's a JS function call. The function
    81  * RunScript(), however, pushes an actual C++ stack pointer onto the SPS stack.
    82  * This way when interleaving C++ and JS, if SPS sees a nullptr native stack
    83  * pointer on the SPS stack, it looks backwards for the first non-nullptr
    84  * pointer and uses that for all subsequent nullptr native stack pointers.
    85  *
    86  * = Line Numbers
    87  *
    88  * One goal of sampling is to get both a backtrace of the JS stack, but also
    89  * know where within each function on the stack execution currently is. For
    90  * this, each ProfileEntry has a 'pc' field to tell where its execution
    91  * currently is. This field is updated whenever a call is made to another JS
    92  * function, and for the JIT it is also updated whenever the JIT is left.
    93  *
    94  * This field is in a union with a uint32_t 'line' so that C++ can make use of
    95  * the field as well. It was observed that tracking 'line' via PCToLineNumber in
    96  * JS was far too expensive, so that is why the pc instead of the translated
    97  * line number is stored.
    98  *
    99  * As an invariant, if the pc is nullptr, then the JIT is currently executing
   100  * generated code. Otherwise execution is in another JS function or in C++. With
   101  * this in place, only the top entry of the stack can ever have nullptr as its
   102  * pc. Additionally with this invariant, it is possible to maintain mappings of
   103  * JIT code to pc which can be accessed safely because they will only be
   104  * accessed from a signal handler when the JIT code is executing.
   105  */
   107 namespace js {
   109 class ProfileEntry;
   111 typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy>
   112         ProfileStringMap;
   114 class SPSEntryMarker;
   116 class SPSProfiler
   117 {
   118     friend class SPSEntryMarker;
   120     JSRuntime            *rt;
   121     ProfileStringMap     strings;
   122     ProfileEntry         *stack_;
   123     uint32_t             *size_;
   124     uint32_t             max_;
   125     bool                 slowAssertions;
   126     uint32_t             enabled_;
   127     PRLock               *lock_;
   128     void                (*eventMarker_)(const char *);
   130     const char *allocProfileString(JSScript *script, JSFunction *function);
   131     void push(const char *string, void *sp, JSScript *script, jsbytecode *pc);
   132     void pushNoCopy(const char *string, void *sp,
   133                     JSScript *script, jsbytecode *pc) {
   134         push(string, reinterpret_cast<void*>(
   135             reinterpret_cast<uintptr_t>(sp) | ProfileEntry::NoCopyBit),
   136             script, pc);
   137     }
   138     void pop();
   140   public:
   141     SPSProfiler(JSRuntime *rt);
   142     ~SPSProfiler();
   144     bool init();
   146     uint32_t **addressOfSizePointer() {
   147         return &size_;
   148     }
   150     uint32_t *addressOfMaxSize() {
   151         return &max_;
   152     }
   154     ProfileEntry **addressOfStack() {
   155         return &stack_;
   156     }
   158     uint32_t *sizePointer() { return size_; }
   159     uint32_t maxSize() { return max_; }
   160     ProfileEntry *stack() { return stack_; }
   162     /* management of whether instrumentation is on or off */
   163     bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; }
   164     bool installed() { return stack_ != nullptr && size_ != nullptr; }
   165     void enable(bool enabled);
   166     void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
   167     bool slowAssertionsEnabled() { return slowAssertions; }
   169     /*
   170      * Functions which are the actual instrumentation to track run information
   171      *
   172      *   - enter: a function has started to execute
   173      *   - updatePC: updates the pc information about where a function
   174      *               is currently executing
   175      *   - exit: this function has ceased execution, and no further
   176      *           entries/exits will be made
   177      */
   178     bool enter(JSScript *script, JSFunction *maybeFun);
   179     void exit(JSScript *script, JSFunction *maybeFun);
   180     void updatePC(JSScript *script, jsbytecode *pc) {
   181         if (enabled() && *size_ - 1 < max_) {
   182             JS_ASSERT(*size_ > 0);
   183             JS_ASSERT(stack_[*size_ - 1].script() == script);
   184             stack_[*size_ - 1].setPC(pc);
   185         }
   186     }
   188     /* Enter a C++ function. */
   189     void enterNative(const char *string, void *sp);
   190     void exitNative() { pop(); }
   192     jsbytecode *ipToPC(JSScript *script, size_t ip) { return nullptr; }
   194     void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max);
   195     void setEventMarker(void (*fn)(const char *));
   196     const char *profileString(JSScript *script, JSFunction *maybeFun);
   197     void onScriptFinalized(JSScript *script);
   199     void markEvent(const char *event);
   201     /* meant to be used for testing, not recommended to call in normal code */
   202     size_t stringsCount();
   203     void stringsReset();
   205     uint32_t *addressOfEnabled() {
   206         return &enabled_;
   207     }
   208 };
   210 /*
   211  * This class is used to make sure the strings table
   212  * is only accessed on one thread at a time.
   213  */
   214 class AutoSPSLock
   215 {
   216   public:
   217 #ifdef JS_THREADSAFE
   218     AutoSPSLock(PRLock *lock)
   219     {
   220         MOZ_ASSERT(lock, "Parameter should not be null!");
   221         lock_ = lock;
   222         PR_Lock(lock);
   223     }
   224     ~AutoSPSLock() { PR_Unlock(lock_); }
   225 #else
   226     AutoSPSLock(PRLock *) {}
   227 #endif
   229   private:
   230     PRLock *lock_;
   231 };
   233 inline size_t
   234 SPSProfiler::stringsCount()
   235 {
   236     AutoSPSLock lock(lock_);
   237     return strings.count();
   238 }
   240 inline void
   241 SPSProfiler::stringsReset()
   242 {
   243     AutoSPSLock lock(lock_);
   244     strings.clear();
   245 }
   247 /*
   248  * This class is used in RunScript() to push the marker onto the sampling stack
   249  * that we're about to enter JS function calls. This is the only time in which a
   250  * valid stack pointer is pushed to the sampling stack.
   251  */
   252 class SPSEntryMarker
   253 {
   254   public:
   255     SPSEntryMarker(JSRuntime *rt
   256                    MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   257     ~SPSEntryMarker();
   259   private:
   260     SPSProfiler *profiler;
   261     mozilla::DebugOnly<uint32_t> size_before;
   262     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   263 };
   265 /*
   266  * SPS is the profiling backend used by the JS engine to enable time profiling.
   267  * More information can be found in vm/SPSProfiler.{h,cpp}. This class manages
   268  * the instrumentation portion of the profiling for JIT code.
   269  *
   270  * The instrumentation tracks entry into functions, leaving those functions via
   271  * a function call, reentering the functions from a function call, and exiting
   272  * the functions from returning. This class also handles inline frames and
   273  * manages the instrumentation which needs to be attached to them as well.
   274  *
   275  * The basic methods which emit instrumentation are at the end of this class,
   276  * and the management functions are all described in the middle.
   277  */
   278 template<class Assembler, class Register>
   279 class SPSInstrumentation
   280 {
   281     /* Because of inline frames, this is a nested structure in a vector */
   282     struct FrameState {
   283         JSScript *script; // script for this frame, nullptr if not pushed yet
   284         jsbytecode *pc;   // pc at which this frame was left for entry into a callee
   285         bool skipNext;    // should the next call to reenter be skipped?
   286         int  left;        // number of leave() calls made without a matching reenter()
   287     };
   289     SPSProfiler *profiler_; // Instrumentation location management
   291     Vector<FrameState, 1, SystemAllocPolicy> frames;
   292     FrameState *frame;
   294     static void clearFrame(FrameState *frame) {
   295         frame->script = nullptr;
   296         frame->pc = nullptr;
   297         frame->skipNext = false;
   298         frame->left = 0;
   299     }
   301   public:
   302     /*
   303      * Creates instrumentation which writes information out the the specified
   304      * profiler's stack and constituent fields.
   305      */
   306     SPSInstrumentation(SPSProfiler *profiler)
   307       : profiler_(profiler), frame(nullptr)
   308     {
   309         enterInlineFrame(nullptr);
   310     }
   312     /* Small proxies around SPSProfiler */
   313     bool enabled() { return profiler_ && profiler_->enabled(); }
   314     SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; }
   315     void disable() { profiler_ = nullptr; }
   317     /* Signals an inline function returned, reverting to the previous state */
   318     void leaveInlineFrame() {
   319         if (!enabled())
   320             return;
   321         JS_ASSERT(frame->left == 0);
   322         JS_ASSERT(frame->script != nullptr);
   323         frames.shrinkBy(1);
   324         JS_ASSERT(frames.length() > 0);
   325         frame = &frames[frames.length() - 1];
   326     }
   328     /* Saves the current state and assumes a fresh one for the inline function */
   329     bool enterInlineFrame(jsbytecode *callerPC) {
   330         if (!enabled())
   331             return true;
   332         JS_ASSERT_IF(frames.empty(), callerPC == nullptr);
   334         JS_ASSERT_IF(frame != nullptr, frame->script != nullptr);
   335         JS_ASSERT_IF(frame != nullptr, frame->left == 1);
   336         if (!frames.empty()) {
   337             JS_ASSERT(frame == &frames[frames.length() - 1]);
   338             frame->pc = callerPC;
   339         }
   340         if (!frames.growBy(1))
   341             return false;
   342         frame = &frames[frames.length() - 1];
   343         clearFrame(frame);
   344         return true;
   345     }
   347     /* Prepares the instrumenter state for generating OOL code, by
   348      * setting up the frame state to seem as if there are exactly
   349      * two pushed frames: a frame for the top-level script, and
   350      * a frame for the OOL code being generated.  Any
   351      * vm-calls from the OOL code will "leave" the OOL frame and
   352      * return back to it.
   353      */
   354     bool prepareForOOL() {
   355         if (!enabled())
   356             return true;
   357         JS_ASSERT(!frames.empty());
   358         if (frames.length() >= 2) {
   359             frames.shrinkBy(frames.length() - 2);
   361         } else { // frames.length() == 1
   362             if (!frames.growBy(1))
   363                 return false;
   364         }
   365         frames[0].pc = frames[0].script->code();
   366         frame = &frames[1];
   367         clearFrame(frame);
   368         return true;
   369     }
   370     void finishOOL() {
   371         if (!enabled())
   372             return;
   373         JS_ASSERT(!frames.empty());
   374         frames.shrinkBy(frames.length() - 1);
   375     }
   377     /* Number of inline frames currently active (doesn't include original one) */
   378     unsigned inliningDepth() {
   379         return frames.length() - 1;
   380     }
   382     /*
   383      * When debugging or with slow assertions, sometimes a C++ method will be
   384      * invoked to perform the pop operation from the SPS stack. When we leave
   385      * JIT code, we need to record the current PC, but upon reentering JIT
   386      * code, no update back to nullptr should happen. This method exists to
   387      * flag this behavior. The next leave() will emit instrumentation, but the
   388      * following reenter() will be a no-op.
   389      */
   390     void skipNextReenter() {
   391         /* If we've left the frame, the reenter will be skipped anyway */
   392         if (!enabled() || frame->left != 0)
   393             return;
   394         JS_ASSERT(frame->script);
   395         JS_ASSERT(!frame->skipNext);
   396         frame->skipNext = true;
   397     }
   399     /*
   400      * In some cases, a frame needs to be flagged as having been pushed, but no
   401      * instrumentation should be emitted. This updates internal state to flag
   402      * that further instrumentation should actually be emitted.
   403      */
   404     void setPushed(JSScript *script) {
   405         if (!enabled())
   406             return;
   407         JS_ASSERT(frame->left == 0);
   408         frame->script = script;
   409     }
   411     /*
   412      * Flags entry into a JS function for the first time. Before this is called,
   413      * no instrumentation is emitted, but after this instrumentation is emitted.
   414      */
   415     bool push(JSScript *script, Assembler &masm, Register scratch, bool inlinedFunction = false) {
   416         if (!enabled())
   417             return true;
   418 #ifdef JS_ION
   419         if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) {
   420 #endif
   421             const char *string = profiler_->profileString(script, script->functionNonDelazifying());
   422             if (string == nullptr)
   423                 return false;
   424             masm.spsPushFrame(profiler_, string, script, scratch);
   425 #ifdef JS_ION
   426         }
   427 #endif
   428         setPushed(script);
   429         return true;
   430     }
   432     /*
   433      * Signifies that C++ performed the push() for this function. C++ always
   434      * sets the current PC to something non-null, however, so as soon as JIT
   435      * code is reentered this updates the current pc to nullptr.
   436      */
   437     void pushManual(JSScript *script, Assembler &masm, Register scratch,
   438                     bool inlinedFunction = false)
   439     {
   440         if (!enabled())
   441             return;
   443 #ifdef JS_ION
   444         if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames)
   445 #endif
   446             masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch);
   448         setPushed(script);
   449     }
   451     /*
   452      * Signals that the current function is leaving for a function call. This
   453      * can happen both on JS function calls and also calls to C++. This
   454      * internally manages how many leave() calls have been seen, and only the
   455      * first leave() emits instrumentation. Similarly, only the last
   456      * corresponding reenter() actually emits instrumentation.
   457      */
   458     void leave(jsbytecode *pc, Assembler &masm, Register scratch, bool inlinedFunction = false) {
   459         if (enabled() && frame->script && frame->left++ == 0) {
   460             jsbytecode *updatePC = pc;
   461             JSScript *script = frame->script;
   462 #ifdef JS_ION
   463             if (!inlinedFunction) {
   464                 // We may be leaving an inlined frame for entry into a C++
   465                 // frame.  If profileInlineFrames is turned off, use the top
   466                 // script's pc offset instead of the innermost script's.
   467                 if (!jit::js_JitOptions.profileInlineFrames && inliningDepth() > 0) {
   468                     JS_ASSERT(frames[0].pc);
   469                     updatePC = frames[0].pc;
   470                     script = frames[0].script;
   471                 }
   472             }
   473 #endif
   475 #ifdef JS_ION
   476             if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames)
   477 #endif
   478                 masm.spsUpdatePCIdx(profiler_, script->pcToOffset(updatePC), scratch);
   479         }
   480     }
   482     /*
   483      * Flags that the leaving of the current function has returned. This tracks
   484      * state with leave() to only emit instrumentation at proper times.
   485      */
   486     void reenter(Assembler &masm, Register scratch, bool inlinedFunction = false) {
   487         if (!enabled() || !frame->script || frame->left-- != 1)
   488             return;
   489         if (frame->skipNext) {
   490             frame->skipNext = false;
   491         } else {
   492 #ifdef JS_ION
   493              if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames)
   494 #endif
   495                  masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch);
   496         }
   497     }
   499     /*
   500      * Signifies exiting a JS frame, popping the SPS entry. Because there can be
   501      * multiple return sites of a function, this does not cease instrumentation
   502      * emission.
   503      */
   504     void pop(Assembler &masm, Register scratch, bool inlinedFunction = false) {
   505         if (enabled()) {
   506             JS_ASSERT(frame->left == 0);
   507             JS_ASSERT(frame->script);
   508 #ifdef JS_ION
   509             if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames)
   510 #endif
   511                 masm.spsPopFrame(profiler_, scratch);
   512         }
   513     }
   514 };
   516 } /* namespace js */
   518 #endif /* vm_SPSProfiler_h */

mercurial