1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef vm_GeckoProfiler_h
8 #define vm_GeckoProfiler_h
9 
10 #include "mozilla/Assertions.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/DebugOnly.h"
13 
14 #include <stddef.h>
15 #include <stdint.h>
16 
17 #include "jspubtd.h"
18 
19 #include "js/AllocPolicy.h"
20 #include "js/HashTable.h"
21 #include "js/ProfilingCategory.h"
22 #include "js/TypeDecls.h"
23 #include "js/Utility.h"
24 #include "threading/ProtectedData.h"
25 
26 /*
27  * Gecko Profiler integration with the JS Engine
28  * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
29  *
30  * The Gecko Profiler (found in tools/profiler) is an implementation of a
31  * profiler which has the ability to walk the C++ stack as well as use
32  * instrumentation to gather information. When dealing with JS, however, the
33  * profiler needs integration with the engine because otherwise it is very
34  * difficult to figure out what javascript is executing.
35  *
36  * The current method of integration with the profiler is a form of
37  * instrumentation: every time a JS function is entered, a bit of information
38  * is pushed onto a stack that the profiler owns and maintains. This
39  * information is then popped at the end of the JS function. The profiler
40  * informs the JS engine of this stack at runtime, and it can by turned on/off
41  * dynamically. Each stack frame has type ProfilingStackFrame.
42  *
43  * Throughout execution, the size of the stack recorded in memory may exceed the
44  * maximum. The JS engine will not write any information past the maximum limit,
45  * but it will still maintain the size of the stack. Profiler code is aware of
46  * this and iterates the stack accordingly.
47  *
48  * There is some information pushed on the profiler stack for every JS function
49  * that is entered. First is a char* label with a description of what function
50  * was entered. Currently this string is of the form "function (file:line)" if
51  * there's a function name, or just "file:line" if there's no function name
52  * available. The other bit of information is the relevant C++ (native) stack
53  * pointer. This stack pointer is what enables the interleaving of the C++ and
54  * the JS stack. Finally, throughout execution of the function, some extra
55  * information may be updated on the ProfilingStackFrame structure.
56  *
57  * = Profile Strings
58  *
59  * The profile strings' allocations and deallocation must be carefully
60  * maintained, and ideally at a very low overhead cost. For this reason, the JS
61  * engine maintains a mapping of all known profile strings. These strings are
62  * keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
63  * JSScript* pair. A JSScript will destroy its corresponding profile string when
64  * the script is finalized.
65  *
66  * For this reason, a char* pointer pushed on the profiler stack is valid only
67  * while it is on the profiler stack. The profiler uses sampling to read off
68  * information from this instrumented stack, and it therefore copies the string
69  * byte for byte when a JS function is encountered during sampling.
70  *
71  * = Native Stack Pointer
72  *
73  * The actual value pushed as the native pointer is nullptr for most JS
74  * functions. The reason for this is that there's actually very little
75  * correlation between the JS stack and the C++ stack because many JS functions
76  * all run in the same C++ frame, or can even go backwards in C++ when going
77  * from the JIT back to the interpreter.
78  *
79  * To alleviate this problem, all JS functions push nullptr as their "native
80  * stack pointer" to indicate that it's a JS function call. The function
81  * RunScript(), however, pushes an actual C++ stack pointer onto the profiler
82  * stack. This way when interleaving C++ and JS, if the Gecko Profiler sees a
83  * nullptr native stack pointer on the profiler stack, it looks backwards for
84  * the first non-nullptr pointer and uses that for all subsequent nullptr
85  * native stack pointers.
86  *
87  * = Line Numbers
88  *
89  * One goal of sampling is to get both a backtrace of the JS stack, but also
90  * know where within each function on the stack execution currently is. For
91  * this, each ProfilingStackFrame has a 'pc' field to tell where its execution
92  * currently is. This field is updated whenever a call is made to another JS
93  * function, and for the JIT it is also updated whenever the JIT is left.
94  *
95  * This field is in a union with a uint32_t 'line' so that C++ can make use of
96  * the field as well. It was observed that tracking 'line' via PCToLineNumber in
97  * JS was far too expensive, so that is why the pc instead of the translated
98  * line number is stored.
99  *
100  * As an invariant, if the pc is nullptr, then the JIT is currently executing
101  * generated code. Otherwise execution is in another JS function or in C++. With
102  * this in place, only the top frame of the stack can ever have nullptr as its
103  * pc. Additionally with this invariant, it is possible to maintain mappings of
104  * JIT code to pc which can be accessed safely because they will only be
105  * accessed from a signal handler when the JIT code is executing.
106  */
107 
108 namespace js {
109 
110 class BaseScript;
111 class GeckoProfilerThread;
112 
113 // The `ProfileStringMap` weakly holds its `BaseScript*` keys and owns its
114 // string values. Entries are removed when the `BaseScript` is finalized; see
115 // `GeckoProfiler::onScriptFinalized`.
116 using ProfileStringMap = HashMap<BaseScript*, JS::UniqueChars,
117                                  DefaultHasher<BaseScript*>, SystemAllocPolicy>;
118 
119 class GeckoProfilerRuntime {
120   JSRuntime* rt;
121   MainThreadData<ProfileStringMap> strings_;
122   bool slowAssertions;
123   uint32_t enabled_;
124   void (*eventMarker_)(const char*, const char*);
125 
126  public:
127   explicit GeckoProfilerRuntime(JSRuntime* rt);
128 
129   /* management of whether instrumentation is on or off */
enabled()130   bool enabled() { return enabled_; }
131   void enable(bool enabled);
enableSlowAssertions(bool enabled)132   void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
slowAssertionsEnabled()133   bool slowAssertionsEnabled() { return slowAssertions; }
134 
135   void setEventMarker(void (*fn)(const char*, const char*));
136 
137   static JS::UniqueChars allocProfileString(JSContext* cx, BaseScript* script);
138   const char* profileString(JSContext* cx, BaseScript* script);
139 
140   void onScriptFinalized(BaseScript* script);
141 
142   void markEvent(const char* event, const char* details);
143 
strings()144   ProfileStringMap& strings() { return strings_.ref(); }
145 
146   /* meant to be used for testing, not recommended to call in normal code */
147   size_t stringsCount();
148   void stringsReset();
149 
addressOfEnabled()150   uint32_t* addressOfEnabled() { return &enabled_; }
151 
152   void fixupStringsMapAfterMovingGC();
153 #ifdef JSGC_HASH_TABLE_CHECKS
154   void checkStringsMapAfterMovingGC();
155 #endif
156 };
157 
stringsCount()158 inline size_t GeckoProfilerRuntime::stringsCount() { return strings().count(); }
159 
stringsReset()160 inline void GeckoProfilerRuntime::stringsReset() { strings().clear(); }
161 
162 /*
163  * This class is used in RunScript() to push the marker onto the sampling stack
164  * that we're about to enter JS function calls. This is the only time in which a
165  * valid stack pointer is pushed to the sampling stack.
166  */
167 class MOZ_RAII GeckoProfilerEntryMarker {
168  public:
169   explicit MOZ_ALWAYS_INLINE GeckoProfilerEntryMarker(JSContext* cx,
170                                                       JSScript* script);
171   MOZ_ALWAYS_INLINE ~GeckoProfilerEntryMarker();
172 
173  private:
174   GeckoProfilerThread* profiler_;
175 #ifdef DEBUG
176   uint32_t spBefore_;
177 #endif
178 };
179 
180 /*
181  * RAII class to automatically add Gecko Profiler profiling stack frames.
182  *
183  * NB: The `label` string must be statically allocated.
184  */
185 class MOZ_NONHEAP_CLASS AutoGeckoProfilerEntry {
186  public:
187   explicit MOZ_ALWAYS_INLINE AutoGeckoProfilerEntry(
188       JSContext* cx, const char* label,
189       JS::ProfilingCategoryPair categoryPair = JS::ProfilingCategoryPair::JS,
190       uint32_t flags = 0);
191   MOZ_ALWAYS_INLINE ~AutoGeckoProfilerEntry();
192 
193  private:
194   GeckoProfilerThread* profiler_;
195 #ifdef DEBUG
196   uint32_t spBefore_;
197 #endif
198 };
199 
200 /*
201  * This class is used in the interpreter to bound regions where the baseline JIT
202  * being entered via OSR.  It marks the current top profiling stack frame as
203  * OSR-ed
204  */
205 class MOZ_RAII GeckoProfilerBaselineOSRMarker {
206  public:
207   explicit GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame);
208   ~GeckoProfilerBaselineOSRMarker();
209 
210  private:
211   GeckoProfilerThread* profiler;
212   mozilla::DebugOnly<uint32_t> spBefore_;
213 };
214 
215 } /* namespace js */
216 
217 #endif /* vm_GeckoProfiler_h */
218