diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/vm/GeckoProfiler.h | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/vm/GeckoProfiler.h')
-rw-r--r-- | js/src/vm/GeckoProfiler.h | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/js/src/vm/GeckoProfiler.h b/js/src/vm/GeckoProfiler.h new file mode 100644 index 0000000000..9fcbfed9a5 --- /dev/null +++ b/js/src/vm/GeckoProfiler.h @@ -0,0 +1,255 @@ +/* -*- 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 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 + * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler + * + * 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() { 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(); + + uint32_t* addressOfEnabled() { 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 */ |