341 lines
12 KiB
C++
341 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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 "mozilla/DebugOnly.h"
|
|
#include "mozilla/GuardObjects.h"
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "jsscript.h"
|
|
|
|
#include "js/ProfilingStack.h"
|
|
#include "threading/ExclusiveData.h"
|
|
#include "vm/MutexIDs.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 nullptr 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 nullptr 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 nullptr native stack
|
|
* pointer on the SPS stack, it looks backwards for the first non-nullptr
|
|
* pointer and uses that for all subsequent nullptr 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 nullptr, 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 nullptr 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.
|
|
*/
|
|
|
|
namespace js {
|
|
|
|
// The `ProfileStringMap` weakly holds its `JSScript*` keys and owns its string
|
|
// values. Entries are removed when the `JSScript` is finalized; see
|
|
// `SPSProfiler::onScriptFinalized`.
|
|
using ProfileStringMap = HashMap<JSScript*,
|
|
UniqueChars,
|
|
DefaultHasher<JSScript*>,
|
|
SystemAllocPolicy>;
|
|
|
|
class AutoSPSEntry;
|
|
class SPSEntryMarker;
|
|
class SPSBaselineOSRMarker;
|
|
|
|
class SPSProfiler
|
|
{
|
|
friend class AutoSPSEntry;
|
|
friend class SPSEntryMarker;
|
|
friend class SPSBaselineOSRMarker;
|
|
|
|
JSRuntime* rt;
|
|
ExclusiveData<ProfileStringMap> strings;
|
|
ProfileEntry* stack_;
|
|
uint32_t* size_;
|
|
uint32_t max_;
|
|
bool slowAssertions;
|
|
uint32_t enabled_;
|
|
void (*eventMarker_)(const char*);
|
|
|
|
UniqueChars allocProfileString(JSScript* script, JSFunction* function);
|
|
void push(const char* string, void* sp, JSScript* script, jsbytecode* pc, bool copy,
|
|
ProfileEntry::Category category = ProfileEntry::Category::JS);
|
|
void pop();
|
|
|
|
public:
|
|
explicit SPSProfiler(JSRuntime* rt);
|
|
|
|
bool init();
|
|
|
|
uint32_t** addressOfSizePointer() {
|
|
return &size_;
|
|
}
|
|
|
|
uint32_t* addressOfMaxSize() {
|
|
return &max_;
|
|
}
|
|
|
|
ProfileEntry** addressOfStack() {
|
|
return &stack_;
|
|
}
|
|
|
|
uint32_t* sizePointer() { return size_; }
|
|
uint32_t maxSize() { return max_; }
|
|
uint32_t size() { MOZ_ASSERT(installed()); return *size_; }
|
|
ProfileEntry* stack() { return stack_; }
|
|
|
|
/* management of whether instrumentation is on or off */
|
|
bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; }
|
|
bool installed() { return stack_ != nullptr && size_ != nullptr; }
|
|
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(JSScript* script, JSFunction* maybeFun);
|
|
void updatePC(JSScript* script, jsbytecode* pc) {
|
|
if (enabled() && *size_ - 1 < max_) {
|
|
MOZ_ASSERT(*size_ > 0);
|
|
MOZ_ASSERT(stack_[*size_ - 1].rawScript() == script);
|
|
stack_[*size_ - 1].setPC(pc);
|
|
}
|
|
}
|
|
|
|
/* Enter wasm code */
|
|
void beginPseudoJS(const char* string, void* sp);
|
|
void endPseudoJS() { pop(); }
|
|
|
|
jsbytecode* ipToPC(JSScript* script, size_t ip) { return nullptr; }
|
|
|
|
void setProfilingStack(ProfileEntry* stack, uint32_t* size, uint32_t max);
|
|
void setEventMarker(void (*fn)(const char*));
|
|
const char* profileString(JSScript* script, JSFunction* maybeFun);
|
|
void onScriptFinalized(JSScript* script);
|
|
|
|
void markEvent(const char* event);
|
|
|
|
/* meant to be used for testing, not recommended to call in normal code */
|
|
size_t stringsCount();
|
|
void stringsReset();
|
|
|
|
uint32_t* addressOfEnabled() {
|
|
return &enabled_;
|
|
}
|
|
|
|
void trace(JSTracer* trc);
|
|
void fixupStringsMapAfterMovingGC();
|
|
#ifdef JSGC_HASH_TABLE_CHECKS
|
|
void checkStringsMapAfterMovingGC();
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* This class is used to suppress profiler sampling during
|
|
* critical sections where stack state is not valid.
|
|
*/
|
|
class MOZ_RAII AutoSuppressProfilerSampling
|
|
{
|
|
public:
|
|
explicit AutoSuppressProfilerSampling(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
|
|
explicit AutoSuppressProfilerSampling(JSRuntime* rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
|
|
|
|
~AutoSuppressProfilerSampling();
|
|
|
|
private:
|
|
JSRuntime* rt_;
|
|
bool previouslyEnabled_;
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
};
|
|
|
|
inline size_t
|
|
SPSProfiler::stringsCount()
|
|
{
|
|
return strings.lock()->count();
|
|
}
|
|
|
|
inline void
|
|
SPSProfiler::stringsReset()
|
|
{
|
|
strings.lock()->clear();
|
|
}
|
|
|
|
/*
|
|
* 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 MOZ_RAII SPSEntryMarker
|
|
{
|
|
public:
|
|
explicit SPSEntryMarker(JSRuntime* rt,
|
|
JSScript* script
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
|
|
~SPSEntryMarker();
|
|
|
|
private:
|
|
SPSProfiler* profiler;
|
|
mozilla::DebugOnly<uint32_t> size_before;
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
};
|
|
|
|
/*
|
|
* RAII class to automatically add SPS psuedo frame entries.
|
|
*
|
|
* NB: The `label` string must be statically allocated.
|
|
*/
|
|
class MOZ_NONHEAP_CLASS AutoSPSEntry
|
|
{
|
|
public:
|
|
explicit AutoSPSEntry(JSRuntime* rt, const char* label,
|
|
ProfileEntry::Category category = ProfileEntry::Category::JS
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
|
|
~AutoSPSEntry();
|
|
|
|
private:
|
|
SPSProfiler* profiler_;
|
|
mozilla::DebugOnly<uint32_t> sizeBefore_;
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
};
|
|
|
|
/*
|
|
* This class is used in the interpreter to bound regions where the baseline JIT
|
|
* being entered via OSR. It marks the current top pseudostack entry as
|
|
* OSR-ed
|
|
*/
|
|
class MOZ_RAII SPSBaselineOSRMarker
|
|
{
|
|
public:
|
|
explicit SPSBaselineOSRMarker(JSRuntime* rt, bool hasSPSFrame
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
|
|
~SPSBaselineOSRMarker();
|
|
|
|
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
|
|
{
|
|
SPSProfiler* profiler_; // Instrumentation location management
|
|
|
|
public:
|
|
/*
|
|
* Creates instrumentation which writes information out the the specified
|
|
* profiler's stack and constituent fields.
|
|
*/
|
|
explicit SPSInstrumentation(SPSProfiler* profiler) : profiler_(profiler) {}
|
|
|
|
/* Small proxies around SPSProfiler */
|
|
bool enabled() { return profiler_ && profiler_->enabled(); }
|
|
SPSProfiler* profiler() { MOZ_ASSERT(enabled()); return profiler_; }
|
|
void disable() { profiler_ = nullptr; }
|
|
};
|
|
|
|
|
|
/* Get a pointer to the top-most profiling frame, given the exit frame pointer. */
|
|
void* GetTopProfilingJitFrame(uint8_t* exitFramePtr);
|
|
|
|
} /* namespace js */
|
|
|
|
#endif /* vm_SPSProfiler_h */
|