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