Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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
#ifndef vm_GeckoProfiler_h
#define vm_GeckoProfiler_h
#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include <stddef.h>
#include <stdint.h>
#include "jspubtd.h"
#include "js/AllocPolicy.h"
#include "js/HashTable.h"
#include "js/ProfilingCategory.h"
#include "js/TypeDecls.h"
#include "js/Utility.h"
#include "threading/ProtectedData.h"
/*
* Gecko Profiler integration with the JS Engine
*
* The Gecko 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, the
* profiler needs integration with the engine because otherwise it is very
* difficult to figure out what javascript is executing.
*
* The current method of integration with the profiler is a form of
* instrumentation: every time a JS function is entered, a bit of information
* is pushed onto a stack that the profiler owns and maintains. This
* information is then popped at the end of the JS function. The profiler
* informs the JS engine of this stack at runtime, and it can by turned on/off
* dynamically. Each stack frame has type ProfilingStackFrame.
*
* 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. Profiler code is aware of
* this and iterates the stack accordingly.
*
* There is some information pushed on the profiler stack for every JS function
* that is entered. First is a char* label with 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 ProfilingStackFrame 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 profiler stack is valid only
* while it is on the profiler stack. The profiler 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 profiler
* stack. This way when interleaving C++ and JS, if the Gecko Profiler sees a
* nullptr native stack pointer on the profiler 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 ProfilingStackFrame 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 frame 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.
*/
class JS_PUBLIC_API ProfilingStack;
namespace js {
class BaseScript;
class GeckoProfilerThread;
// The `ProfileStringMap` weakly holds its `BaseScript*` keys and owns its
// string values. Entries are removed when the `BaseScript` is finalized; see
// `GeckoProfiler::onScriptFinalized`.
using ProfileStringMap = HashMap<BaseScript*, JS::UniqueChars,
DefaultHasher<BaseScript*>, SystemAllocPolicy>;
class GeckoProfilerRuntime {
JSRuntime* rt;
MainThreadData<ProfileStringMap> strings_;
bool slowAssertions;
uint32_t enabled_;
void (*eventMarker_)(const char*, const char*);
public:
explicit GeckoProfilerRuntime(JSRuntime* rt);
/* management of whether instrumentation is on or off */
bool enabled() const { return enabled_; }
void enable(bool enabled);
void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
bool slowAssertionsEnabled() { return slowAssertions; }
void setEventMarker(void (*fn)(const char*, const char*));
static JS::UniqueChars allocProfileString(JSContext* cx, BaseScript* script);
const char* profileString(JSContext* cx, BaseScript* script);
void onScriptFinalized(BaseScript* script);
void markEvent(const char* event, const char* details);
ProfileStringMap& strings() { return strings_.ref(); }
/* meant to be used for testing, not recommended to call in normal code */
size_t stringsCount();
void stringsReset();
const uint32_t* addressOfEnabled() const { return &enabled_; }
void fixupStringsMapAfterMovingGC();
#ifdef JSGC_HASH_TABLE_CHECKS
void checkStringsMapAfterMovingGC();
#endif
};
inline size_t GeckoProfilerRuntime::stringsCount() { return strings().count(); }
inline void GeckoProfilerRuntime::stringsReset() { strings().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 GeckoProfilerEntryMarker {
public:
explicit MOZ_ALWAYS_INLINE GeckoProfilerEntryMarker(JSContext* cx,
JSScript* script);
MOZ_ALWAYS_INLINE ~GeckoProfilerEntryMarker();
private:
GeckoProfilerThread* profiler_;
#ifdef DEBUG
uint32_t spBefore_;
#endif
};
/*
* RAII class to automatically add Gecko Profiler profiling stack frames.
* It retrieves the ProfilingStack from the JSContext and does nothing if the
* profiler is inactive.
*
* NB: The `label` string must be statically allocated.
*/
class MOZ_RAII AutoGeckoProfilerEntry {
public:
explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
JSContext* cx, const char* label, const char* dynamicString,
JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
uint32_t flags = 0);
explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
JSContext* cx, const char* label,
JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
uint32_t flags = 0);
MOZ_ALWAYS_INLINE ~AutoGeckoProfilerEntry();
private:
ProfilingStack* profilingStack_;
#ifdef DEBUG
GeckoProfilerThread* profiler_;
uint32_t spBefore_;
#endif
};
/*
* Use this RAII class to add Gecko Profiler label frames for methods of the
* JavaScript builtin API.
* These frames will be exposed to JavaScript developers (ie they won't be
* filtered out when using the "JavaScript" filtering option in the Firefox
* Profiler UI).
* Technical note: the label and dynamicString values will be joined with a dot
* separator if dynamicString is present.
*/
class MOZ_RAII AutoJSMethodProfilerEntry : public AutoGeckoProfilerEntry {
public:
explicit MOZ_ALWAYS_INLINE AutoJSMethodProfilerEntry(
JSContext* cx, const char* label, const char* dynamicString = nullptr);
};
/*
* Use this RAII class to add Gecko Profiler label frames for constructors of
* the JavaScript builtin API.
* These frames will be exposed to JavaScript developers (ie they won't be
* filtered out when using the "JavaScript" filtering option in the Firefox
* Profiler UI).
* Technical note: the word "constructor" will be appended to the label (with a
* space separator).
*/
class MOZ_RAII AutoJSConstructorProfilerEntry : public AutoGeckoProfilerEntry {
public:
explicit MOZ_ALWAYS_INLINE AutoJSConstructorProfilerEntry(JSContext* cx,
const char* label);
};
/*
* This class is used in the interpreter to bound regions where the baseline JIT
* being entered via OSR. It marks the current top profiling stack frame as
* OSR-ed
*/
class MOZ_RAII GeckoProfilerBaselineOSRMarker {
public:
explicit GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame);
~GeckoProfilerBaselineOSRMarker();
private:
GeckoProfilerThread* profiler;
mozilla::DebugOnly<uint32_t> spBefore_;
};
} /* namespace js */
#endif /* vm_GeckoProfiler_h */