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 js_ProfilingStack_h
8 : #define js_ProfilingStack_h
9 :
10 : #include <algorithm>
11 : #include <stdint.h>
12 :
13 : #include "jsbytecode.h"
14 : #include "jstypes.h"
15 : #include "js/TypeDecls.h"
16 : #include "js/Utility.h"
17 :
18 : struct JSRuntime;
19 : class JSTracer;
20 :
21 : class PseudoStack;
22 :
23 : namespace js {
24 :
25 : // A call stack can be specified to the JS engine such that all JS entry/exits
26 : // to functions push/pop an entry to/from the specified stack.
27 : //
28 : // For more detailed information, see vm/GeckoProfiler.h.
29 : //
30 : class ProfileEntry
31 : {
32 : // A ProfileEntry represents either a C++ profile entry or a JS one.
33 :
34 : // Descriptive label for this entry. Must be a static string! Can be an
35 : // empty string, but not a null pointer.
36 : const char* label_;
37 :
38 : // An additional descriptive string of this entry which is combined with
39 : // |label_| in profiler output. Need not be (and usually isn't) static. Can
40 : // be null.
41 : const char* dynamicString_;
42 :
43 : // Stack pointer for non-JS entries, the script pointer otherwise.
44 : void* spOrScript;
45 :
46 : // Line number for non-JS entries, the bytecode offset otherwise.
47 : int32_t lineOrPcOffset;
48 :
49 : // Bits 0..1 hold the Kind. Bits 2..3 are unused. Bits 4..12 hold the
50 : // Category.
51 : uint32_t kindAndCategory_;
52 :
53 : static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc);
54 :
55 : public:
56 : enum class Kind : uint32_t {
57 : // A normal C++ frame.
58 : CPP_NORMAL = 0,
59 :
60 : // A special C++ frame indicating the start of a run of JS pseudostack
61 : // entries. CPP_MARKER_FOR_JS frames are ignored, except for the sp
62 : // field.
63 : CPP_MARKER_FOR_JS = 1,
64 :
65 : // A normal JS frame.
66 : JS_NORMAL = 2,
67 :
68 : // An interpreter JS frame that has OSR-ed into baseline. JS_NORMAL
69 : // frames can be converted to JS_OSR and back. JS_OSR frames are
70 : // ignored.
71 : JS_OSR = 3,
72 :
73 : KIND_MASK = 0x3,
74 : };
75 :
76 : // Keep these in sync with devtools/client/performance/modules/categories.js
77 : enum class Category : uint32_t {
78 : OTHER = 1u << 4,
79 : CSS = 1u << 5,
80 : JS = 1u << 6,
81 : GC = 1u << 7,
82 : CC = 1u << 8,
83 : NETWORK = 1u << 9,
84 : GRAPHICS = 1u << 10,
85 : STORAGE = 1u << 11,
86 : EVENTS = 1u << 12,
87 :
88 : FIRST = OTHER,
89 : LAST = EVENTS,
90 :
91 : CATEGORY_MASK = ~uint32_t(Kind::KIND_MASK),
92 : };
93 :
94 : static_assert((uint32_t(Category::FIRST) & uint32_t(Kind::KIND_MASK)) == 0,
95 : "Category overlaps with Kind");
96 :
97 36216 : bool isCpp() const
98 : {
99 36216 : Kind k = kind();
100 36217 : return k == Kind::CPP_NORMAL || k == Kind::CPP_MARKER_FOR_JS;
101 : }
102 :
103 17773 : bool isJs() const
104 : {
105 17773 : Kind k = kind();
106 17773 : return k == Kind::JS_NORMAL || k == Kind::JS_OSR;
107 : }
108 :
109 : void setLabel(const char* aLabel) { label_ = aLabel; }
110 0 : const char* label() const { return label_; }
111 :
112 0 : const char* dynamicString() const { return dynamicString_; }
113 :
114 36217 : void initCppFrame(const char* aLabel, const char* aDynamicString, void* sp, uint32_t aLine,
115 : Kind aKind, Category aCategory)
116 : {
117 36217 : label_ = aLabel;
118 36217 : dynamicString_ = aDynamicString;
119 36217 : spOrScript = sp;
120 36217 : lineOrPcOffset = static_cast<int32_t>(aLine);
121 36217 : kindAndCategory_ = uint32_t(aKind) | uint32_t(aCategory);
122 36217 : MOZ_ASSERT(isCpp());
123 36217 : }
124 :
125 17386 : void initJsFrame(const char* aLabel, const char* aDynamicString, JSScript* aScript,
126 : jsbytecode* aPc)
127 : {
128 17386 : label_ = aLabel;
129 17386 : dynamicString_ = aDynamicString;
130 17386 : spOrScript = aScript;
131 17386 : lineOrPcOffset = pcToOffset(aScript, aPc);
132 17386 : kindAndCategory_ = uint32_t(Kind::JS_NORMAL) | uint32_t(Category::JS);
133 17386 : MOZ_ASSERT(isJs());
134 17386 : }
135 :
136 0 : void setKind(Kind aKind) {
137 0 : kindAndCategory_ = uint32_t(aKind) | uint32_t(category());
138 0 : }
139 :
140 54070 : Kind kind() const {
141 54070 : return Kind(kindAndCategory_ & uint32_t(Kind::KIND_MASK));
142 : }
143 :
144 0 : Category category() const {
145 0 : return Category(kindAndCategory_ & uint32_t(Category::CATEGORY_MASK));
146 : }
147 :
148 0 : void* stackAddress() const {
149 0 : MOZ_ASSERT(!isJs());
150 0 : return spOrScript;
151 : }
152 :
153 : JS_PUBLIC_API(JSScript*) script() const;
154 :
155 0 : uint32_t line() const {
156 0 : MOZ_ASSERT(!isJs());
157 0 : return static_cast<uint32_t>(lineOrPcOffset);
158 : }
159 :
160 : // Note that the pointer returned might be invalid.
161 101 : JSScript* rawScript() const {
162 101 : MOZ_ASSERT(isJs());
163 101 : return (JSScript*)spOrScript;
164 : }
165 :
166 : // We can't know the layout of JSScript, so look in vm/GeckoProfiler.cpp.
167 : JS_FRIEND_API(jsbytecode*) pc() const;
168 : void setPC(jsbytecode* pc);
169 :
170 : void trace(JSTracer* trc);
171 :
172 : // The offset of a pc into a script's code can actually be 0, so to
173 : // signify a nullptr pc, use a -1 index. This is checked against in
174 : // pc() and setPC() to set/get the right pc.
175 : static const int32_t NullPCOffset = -1;
176 : };
177 :
178 : JS_FRIEND_API(void)
179 : SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack);
180 :
181 : JS_FRIEND_API(void)
182 : EnableContextProfilingStack(JSContext* cx, bool enabled);
183 :
184 : JS_FRIEND_API(void)
185 : RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*));
186 :
187 : } // namespace js
188 :
189 : // Each thread has its own PseudoStack. That thread modifies the PseudoStack,
190 : // pushing and popping elements as necessary.
191 : //
192 : // The PseudoStack is also read periodically by the profiler's sampler thread.
193 : // This happens only when the thread that owns the PseudoStack is suspended. So
194 : // there are no genuine parallel accesses.
195 : //
196 : // However, it is possible for pushing/popping to be interrupted by a periodic
197 : // sample. Because of this, we need pushing/popping to be effectively atomic.
198 : //
199 : // - When pushing a new entry, we increment the stack pointer -- making the new
200 : // entry visible to the sampler thread -- only after the new entry has been
201 : // fully written. The stack pointer is Atomic<> (with SequentiallyConsistent
202 : // semantics) to ensure the incrementing is not reordered before the writes.
203 : //
204 : // - When popping an old entry, the only operation is the decrementing of the
205 : // stack pointer, which is obviously atomic.
206 : //
207 : class PseudoStack
208 : {
209 : public:
210 75 : PseudoStack()
211 75 : : stackPointer(0)
212 75 : {}
213 :
214 2 : ~PseudoStack() {
215 : // The label macros keep a reference to the PseudoStack to avoid a TLS
216 : // access. If these are somehow not all cleared we will get a
217 : // use-after-free so better to crash now.
218 1 : MOZ_RELEASE_ASSERT(stackPointer == 0);
219 1 : }
220 :
221 36219 : void pushCppFrame(const char* label, const char* dynamicString, void* sp, uint32_t line,
222 : js::ProfileEntry::Kind kind, js::ProfileEntry::Category category) {
223 36219 : if (stackPointer < MaxEntries) {
224 36220 : entries[stackPointer].initCppFrame(label, dynamicString, sp, line, kind, category);
225 : }
226 :
227 : // This must happen at the end! The compiler will not reorder this
228 : // update because stackPointer is Atomic.
229 36218 : stackPointer++;
230 36222 : }
231 :
232 17386 : void pushJsFrame(const char* label, const char* dynamicString, JSScript* script,
233 : jsbytecode* pc) {
234 17386 : if (stackPointer < MaxEntries) {
235 17386 : entries[stackPointer].initJsFrame(label, dynamicString, script, pc);
236 : }
237 :
238 : // This must happen at the end! The compiler will not reorder this
239 : // update because stackPointer is Atomic.
240 17386 : stackPointer++;
241 17386 : }
242 :
243 53668 : void pop() {
244 53668 : MOZ_ASSERT(stackPointer > 0);
245 53668 : stackPointer--;
246 53669 : }
247 :
248 22 : uint32_t stackSize() const { return std::min(uint32_t(stackPointer), uint32_t(MaxEntries)); }
249 :
250 : private:
251 : // No copying.
252 : PseudoStack(const PseudoStack&) = delete;
253 : void operator=(const PseudoStack&) = delete;
254 :
255 : public:
256 : static const uint32_t MaxEntries = 1024;
257 :
258 : // The stack entries.
259 : js::ProfileEntry entries[MaxEntries];
260 :
261 : // This may exceed MaxEntries, so instead use the stackSize() method to
262 : // determine the number of valid samples in entries. When this is less
263 : // than MaxEntries, it refers to the first free entry past the top of the
264 : // in-use stack (i.e. entries[stackPointer - 1] is the top stack entry).
265 : mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> stackPointer;
266 : };
267 :
268 : #endif /* js_ProfilingStack_h */
|