summaryrefslogtreecommitdiffstats
path: root/js/src/vm/SPSProfiler.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/SPSProfiler.h')
-rw-r--r--js/src/vm/SPSProfiler.h341
1 files changed, 341 insertions, 0 deletions
diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h
new file mode 100644
index 000000000..b3d00a6d8
--- /dev/null
+++ b/js/src/vm/SPSProfiler.h
@@ -0,0 +1,341 @@
+/* -*- 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 "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 */