Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sts=4 et sw=4 tw=99:
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/DebugOnly.h"
11 : #include "mozilla/GuardObjects.h"
12 :
13 : #include <stddef.h>
14 :
15 : #include "jsscript.h"
16 :
17 : #include "js/ProfilingStack.h"
18 : #include "threading/ExclusiveData.h"
19 : #include "vm/MutexIDs.h"
20 :
21 : /*
22 : * Gecko Profiler integration with the JS Engine
23 : * https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
24 : *
25 : * The Gecko Profiler (found in tools/profiler) is an implementation of a
26 : * profiler which has the ability to walk the C++ stack as well as use
27 : * instrumentation to gather information. When dealing with JS, however, the
28 : * profiler needs integration with the engine because otherwise it is very
29 : * difficult to figure out what javascript is executing.
30 : *
31 : * The current method of integration with the profiler is a form of
32 : * instrumentation: every time a JS function is entered, a bit of information
33 : * is pushed onto a stack that the profiler owns and maintains. This
34 : * information is then popped at the end of the JS function. The profiler
35 : * informs the JS engine of this stack at runtime, and it can by turned on/off
36 : * dynamically. Each stack entry has type ProfileEntry.
37 : *
38 : * Throughout execution, the size of the stack recorded in memory may exceed the
39 : * maximum. The JS engine will not write any information past the maximum limit,
40 : * but it will still maintain the size of the stack. Profiler code is aware of
41 : * this and iterates the stack accordingly.
42 : *
43 : * There is some information pushed on the profiler stack for every JS function
44 : * that is entered. First is a char* label with a description of what function
45 : * was entered. Currently this string is of the form "function (file:line)" if
46 : * there's a function name, or just "file:line" if there's no function name
47 : * available. The other bit of information is the relevant C++ (native) stack
48 : * pointer. This stack pointer is what enables the interleaving of the C++ and
49 : * the JS stack. Finally, throughout execution of the function, some extra
50 : * information may be updated on the ProfileEntry structure.
51 : *
52 : * = Profile Strings
53 : *
54 : * The profile strings' allocations and deallocation must be carefully
55 : * maintained, and ideally at a very low overhead cost. For this reason, the JS
56 : * engine maintains a mapping of all known profile strings. These strings are
57 : * keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
58 : * JSScript* pair. A JSScript will destroy its corresponding profile string when
59 : * the script is finalized.
60 : *
61 : * For this reason, a char* pointer pushed on the profiler stack is valid only
62 : * while it is on the profiler stack. The profiler uses sampling to read off
63 : * information from this instrumented stack, and it therefore copies the string
64 : * byte for byte when a JS function is encountered during sampling.
65 : *
66 : * = Native Stack Pointer
67 : *
68 : * The actual value pushed as the native pointer is nullptr for most JS
69 : * functions. The reason for this is that there's actually very little
70 : * correlation between the JS stack and the C++ stack because many JS functions
71 : * all run in the same C++ frame, or can even go backwards in C++ when going
72 : * from the JIT back to the interpreter.
73 : *
74 : * To alleviate this problem, all JS functions push nullptr as their "native
75 : * stack pointer" to indicate that it's a JS function call. The function
76 : * RunScript(), however, pushes an actual C++ stack pointer onto the profiler
77 : * stack. This way when interleaving C++ and JS, if the Gecko Profiler sees a
78 : * nullptr native stack pointer on the profiler stack, it looks backwards for
79 : * the first non-nullptr pointer and uses that for all subsequent nullptr
80 : * native stack pointers.
81 : *
82 : * = Line Numbers
83 : *
84 : * One goal of sampling is to get both a backtrace of the JS stack, but also
85 : * know where within each function on the stack execution currently is. For
86 : * this, each ProfileEntry has a 'pc' field to tell where its execution
87 : * currently is. This field is updated whenever a call is made to another JS
88 : * function, and for the JIT it is also updated whenever the JIT is left.
89 : *
90 : * This field is in a union with a uint32_t 'line' so that C++ can make use of
91 : * the field as well. It was observed that tracking 'line' via PCToLineNumber in
92 : * JS was far too expensive, so that is why the pc instead of the translated
93 : * line number is stored.
94 : *
95 : * As an invariant, if the pc is nullptr, then the JIT is currently executing
96 : * generated code. Otherwise execution is in another JS function or in C++. With
97 : * this in place, only the top entry of the stack can ever have nullptr as its
98 : * pc. Additionally with this invariant, it is possible to maintain mappings of
99 : * JIT code to pc which can be accessed safely because they will only be
100 : * accessed from a signal handler when the JIT code is executing.
101 : */
102 :
103 : namespace js {
104 :
105 : // The `ProfileStringMap` weakly holds its `JSScript*` keys and owns its string
106 : // values. Entries are removed when the `JSScript` is finalized; see
107 : // `GeckoProfiler::onScriptFinalized`.
108 : using ProfileStringMap = HashMap<JSScript*,
109 : UniqueChars,
110 : DefaultHasher<JSScript*>,
111 : SystemAllocPolicy>;
112 :
113 : class AutoGeckoProfilerEntry;
114 : class GeckoProfilerEntryMarker;
115 : class GeckoProfilerBaselineOSRMarker;
116 :
117 0 : class GeckoProfiler
118 : {
119 : friend class AutoGeckoProfilerEntry;
120 : friend class GeckoProfilerEntryMarker;
121 : friend class GeckoProfilerBaselineOSRMarker;
122 :
123 : JSRuntime* rt;
124 : ExclusiveData<ProfileStringMap> strings;
125 : PseudoStack* pseudoStack_;
126 : bool slowAssertions;
127 : uint32_t enabled_;
128 : void (*eventMarker_)(const char*);
129 :
130 : UniqueChars allocProfileString(JSScript* script, JSFunction* function);
131 :
132 : public:
133 : explicit GeckoProfiler(JSRuntime* rt);
134 :
135 : bool init();
136 :
137 47741 : uint32_t stackPointer() { MOZ_ASSERT(installed()); return pseudoStack_->stackPointer; }
138 0 : ProfileEntry* stack() { return pseudoStack_->entries; }
139 :
140 : /* management of whether instrumentation is on or off */
141 15109 : bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; }
142 71617 : bool installed() { return pseudoStack_ != nullptr; }
143 : MOZ_MUST_USE bool enable(bool enabled);
144 : void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
145 0 : bool slowAssertionsEnabled() { return slowAssertions; }
146 :
147 : /*
148 : * Functions which are the actual instrumentation to track run information
149 : *
150 : * - enter: a function has started to execute
151 : * - updatePC: updates the pc information about where a function
152 : * is currently executing
153 : * - exit: this function has ceased execution, and no further
154 : * entries/exits will be made
155 : */
156 : bool enter(JSContext* cx, JSScript* script, JSFunction* maybeFun);
157 : void exit(JSScript* script, JSFunction* maybeFun);
158 0 : void updatePC(JSScript* script, jsbytecode* pc) {
159 0 : if (!enabled())
160 0 : return;
161 :
162 0 : uint32_t sp = pseudoStack_->stackPointer;
163 0 : if (sp - 1 < PseudoStack::MaxEntries) {
164 0 : MOZ_ASSERT(sp > 0);
165 0 : MOZ_ASSERT(pseudoStack_->entries[sp - 1].rawScript() == script);
166 0 : pseudoStack_->entries[sp - 1].setPC(pc);
167 : }
168 : }
169 :
170 : void setProfilingStack(PseudoStack* pseudoStack);
171 : void setEventMarker(void (*fn)(const char*));
172 : const char* profileString(JSScript* script, JSFunction* maybeFun);
173 : void onScriptFinalized(JSScript* script);
174 :
175 : void markEvent(const char* event);
176 :
177 : /* meant to be used for testing, not recommended to call in normal code */
178 : size_t stringsCount();
179 : void stringsReset();
180 :
181 62 : uint32_t* addressOfEnabled() {
182 62 : return &enabled_;
183 : }
184 :
185 : void trace(JSTracer* trc);
186 : void fixupStringsMapAfterMovingGC();
187 : #ifdef JSGC_HASH_TABLE_CHECKS
188 : void checkStringsMapAfterMovingGC();
189 : #endif
190 : };
191 :
192 : inline size_t
193 : GeckoProfiler::stringsCount()
194 : {
195 : return strings.lock()->count();
196 : }
197 :
198 : inline void
199 : GeckoProfiler::stringsReset()
200 : {
201 : strings.lock()->clear();
202 : }
203 :
204 : /*
205 : * This class is used in RunScript() to push the marker onto the sampling stack
206 : * that we're about to enter JS function calls. This is the only time in which a
207 : * valid stack pointer is pushed to the sampling stack.
208 : */
209 : class MOZ_RAII GeckoProfilerEntryMarker
210 : {
211 : public:
212 : explicit GeckoProfilerEntryMarker(JSRuntime* rt,
213 : JSScript* script
214 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
215 : ~GeckoProfilerEntryMarker();
216 :
217 : private:
218 : GeckoProfiler* profiler;
219 : mozilla::DebugOnly<uint32_t> spBefore_;
220 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
221 : };
222 :
223 : /*
224 : * RAII class to automatically add Gecko Profiler pseudo frame entries.
225 : *
226 : * NB: The `label` string must be statically allocated.
227 : */
228 : class MOZ_NONHEAP_CLASS AutoGeckoProfilerEntry
229 : {
230 : public:
231 : explicit AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
232 : ProfileEntry::Category category = ProfileEntry::Category::JS
233 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
234 : ~AutoGeckoProfilerEntry();
235 :
236 : private:
237 : GeckoProfiler* profiler_;
238 : mozilla::DebugOnly<uint32_t> spBefore_;
239 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
240 : };
241 :
242 : /*
243 : * This class is used in the interpreter to bound regions where the baseline JIT
244 : * being entered via OSR. It marks the current top pseudostack entry as
245 : * OSR-ed
246 : */
247 : class MOZ_RAII GeckoProfilerBaselineOSRMarker
248 : {
249 : public:
250 : explicit GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
251 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
252 : ~GeckoProfilerBaselineOSRMarker();
253 :
254 : private:
255 : GeckoProfiler* profiler;
256 : mozilla::DebugOnly<uint32_t> spBefore_;
257 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
258 : };
259 :
260 : /*
261 : * This class manages the instrumentation portion of the profiling for JIT
262 : * code.
263 : *
264 : * The instrumentation tracks entry into functions, leaving those functions via
265 : * a function call, reentering the functions from a function call, and exiting
266 : * the functions from returning. This class also handles inline frames and
267 : * manages the instrumentation which needs to be attached to them as well.
268 : *
269 : * The basic methods which emit instrumentation are at the end of this class,
270 : * and the management functions are all described in the middle.
271 : */
272 : template<class Assembler, class Register>
273 : class GeckoProfilerInstrumentation
274 : {
275 : GeckoProfiler* profiler_; // Instrumentation location management
276 :
277 : public:
278 : /*
279 : * Creates instrumentation which writes information out the the specified
280 : * profiler's stack and constituent fields.
281 : */
282 : explicit GeckoProfilerInstrumentation(GeckoProfiler* profiler) : profiler_(profiler) {}
283 :
284 : /* Small proxies around GeckoProfiler */
285 : bool enabled() { return profiler_ && profiler_->enabled(); }
286 : GeckoProfiler* profiler() { MOZ_ASSERT(enabled()); return profiler_; }
287 : void disable() { profiler_ = nullptr; }
288 : };
289 :
290 : } /* namespace js */
291 :
292 : #endif /* vm_GeckoProfiler_h */
|