|
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 #include "vm/SPSProfiler.h" |
|
8 |
|
9 #include "mozilla/DebugOnly.h" |
|
10 |
|
11 #include "jsnum.h" |
|
12 #include "jsprf.h" |
|
13 #include "jsscript.h" |
|
14 |
|
15 #include "jit/BaselineJIT.h" |
|
16 #include "vm/StringBuffer.h" |
|
17 |
|
18 using namespace js; |
|
19 |
|
20 using mozilla::DebugOnly; |
|
21 |
|
22 SPSProfiler::SPSProfiler(JSRuntime *rt) |
|
23 : rt(rt), |
|
24 stack_(nullptr), |
|
25 size_(nullptr), |
|
26 max_(0), |
|
27 slowAssertions(false), |
|
28 enabled_(false), |
|
29 lock_(nullptr), |
|
30 eventMarker_(nullptr) |
|
31 { |
|
32 JS_ASSERT(rt != nullptr); |
|
33 } |
|
34 |
|
35 bool |
|
36 SPSProfiler::init() |
|
37 { |
|
38 #ifdef JS_THREADSAFE |
|
39 lock_ = PR_NewLock(); |
|
40 if (lock_ == nullptr) |
|
41 return false; |
|
42 #endif |
|
43 return true; |
|
44 } |
|
45 |
|
46 SPSProfiler::~SPSProfiler() |
|
47 { |
|
48 if (strings.initialized()) { |
|
49 for (ProfileStringMap::Enum e(strings); !e.empty(); e.popFront()) |
|
50 js_free(const_cast<char *>(e.front().value())); |
|
51 } |
|
52 #ifdef JS_THREADSAFE |
|
53 if (lock_) |
|
54 PR_DestroyLock(lock_); |
|
55 #endif |
|
56 } |
|
57 |
|
58 void |
|
59 SPSProfiler::setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max) |
|
60 { |
|
61 AutoSPSLock lock(lock_); |
|
62 JS_ASSERT_IF(size_ && *size_ != 0, !enabled()); |
|
63 if (!strings.initialized()) |
|
64 strings.init(); |
|
65 stack_ = stack; |
|
66 size_ = size; |
|
67 max_ = max; |
|
68 } |
|
69 |
|
70 void |
|
71 SPSProfiler::setEventMarker(void (*fn)(const char *)) |
|
72 { |
|
73 eventMarker_ = fn; |
|
74 } |
|
75 |
|
76 void |
|
77 SPSProfiler::enable(bool enabled) |
|
78 { |
|
79 JS_ASSERT(installed()); |
|
80 |
|
81 if (enabled_ == enabled) |
|
82 return; |
|
83 |
|
84 /* |
|
85 * Ensure all future generated code will be instrumented, or that all |
|
86 * currently instrumented code is discarded |
|
87 */ |
|
88 ReleaseAllJITCode(rt->defaultFreeOp()); |
|
89 |
|
90 enabled_ = enabled; |
|
91 |
|
92 #ifdef JS_ION |
|
93 /* Toggle SPS-related jumps on baseline jitcode. |
|
94 * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not |
|
95 * jitcode for scripts with active frames on the stack. These scripts need to have |
|
96 * their profiler state toggled so they behave properly. |
|
97 */ |
|
98 jit::ToggleBaselineSPS(rt, enabled); |
|
99 #endif |
|
100 } |
|
101 |
|
102 /* Lookup the string for the function/script, creating one if necessary */ |
|
103 const char* |
|
104 SPSProfiler::profileString(JSScript *script, JSFunction *maybeFun) |
|
105 { |
|
106 AutoSPSLock lock(lock_); |
|
107 JS_ASSERT(strings.initialized()); |
|
108 ProfileStringMap::AddPtr s = strings.lookupForAdd(script); |
|
109 if (s) |
|
110 return s->value(); |
|
111 const char *str = allocProfileString(script, maybeFun); |
|
112 if (str == nullptr) |
|
113 return nullptr; |
|
114 if (!strings.add(s, script, str)) { |
|
115 js_free(const_cast<char *>(str)); |
|
116 return nullptr; |
|
117 } |
|
118 return str; |
|
119 } |
|
120 |
|
121 void |
|
122 SPSProfiler::onScriptFinalized(JSScript *script) |
|
123 { |
|
124 /* |
|
125 * This function is called whenever a script is destroyed, regardless of |
|
126 * whether profiling has been turned on, so don't invoke a function on an |
|
127 * invalid hash set. Also, even if profiling was enabled but then turned |
|
128 * off, we still want to remove the string, so no check of enabled() is |
|
129 * done. |
|
130 */ |
|
131 AutoSPSLock lock(lock_); |
|
132 if (!strings.initialized()) |
|
133 return; |
|
134 if (ProfileStringMap::Ptr entry = strings.lookup(script)) { |
|
135 const char *tofree = entry->value(); |
|
136 strings.remove(entry); |
|
137 js_free(const_cast<char *>(tofree)); |
|
138 } |
|
139 } |
|
140 |
|
141 void |
|
142 SPSProfiler::markEvent(const char *event) |
|
143 { |
|
144 JS_ASSERT(enabled()); |
|
145 if (eventMarker_) { |
|
146 JS::AutoAssertNoGC nogc; |
|
147 eventMarker_(event); |
|
148 } |
|
149 } |
|
150 |
|
151 bool |
|
152 SPSProfiler::enter(JSScript *script, JSFunction *maybeFun) |
|
153 { |
|
154 const char *str = profileString(script, maybeFun); |
|
155 if (str == nullptr) |
|
156 return false; |
|
157 |
|
158 #ifdef DEBUG |
|
159 // In debug builds, assert the JS pseudo frames already on the stack |
|
160 // have a non-null pc. Only look at the top frames to avoid quadratic |
|
161 // behavior. |
|
162 if (*size_ > 0 && *size_ - 1 < max_) { |
|
163 size_t start = (*size_ > 4) ? *size_ - 4 : 0; |
|
164 for (size_t i = start; i < *size_ - 1; i++) |
|
165 MOZ_ASSERT_IF(stack_[i].js(), stack_[i].pc() != nullptr); |
|
166 } |
|
167 #endif |
|
168 |
|
169 push(str, nullptr, script, script->code()); |
|
170 return true; |
|
171 } |
|
172 |
|
173 void |
|
174 SPSProfiler::exit(JSScript *script, JSFunction *maybeFun) |
|
175 { |
|
176 pop(); |
|
177 |
|
178 #ifdef DEBUG |
|
179 /* Sanity check to make sure push/pop balanced */ |
|
180 if (*size_ < max_) { |
|
181 const char *str = profileString(script, maybeFun); |
|
182 /* Can't fail lookup because we should already be in the set */ |
|
183 JS_ASSERT(str != nullptr); |
|
184 |
|
185 // Bug 822041 |
|
186 if (!stack_[*size_].js()) { |
|
187 fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); |
|
188 fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_); |
|
189 for (int32_t i = *size_; i >= 0; i--) { |
|
190 if (stack_[i].js()) |
|
191 fprintf(stderr, " [%d] JS %s\n", i, stack_[i].label()); |
|
192 else |
|
193 fprintf(stderr, " [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label()); |
|
194 } |
|
195 } |
|
196 |
|
197 JS_ASSERT(stack_[*size_].js()); |
|
198 JS_ASSERT(stack_[*size_].script() == script); |
|
199 JS_ASSERT(strcmp((const char*) stack_[*size_].label(), str) == 0); |
|
200 stack_[*size_].setLabel(nullptr); |
|
201 stack_[*size_].setPC(nullptr); |
|
202 } |
|
203 #endif |
|
204 } |
|
205 |
|
206 void |
|
207 SPSProfiler::enterNative(const char *string, void *sp) |
|
208 { |
|
209 /* these operations cannot be re-ordered, so volatile-ize operations */ |
|
210 volatile ProfileEntry *stack = stack_; |
|
211 volatile uint32_t *size = size_; |
|
212 uint32_t current = *size; |
|
213 |
|
214 JS_ASSERT(enabled()); |
|
215 if (current < max_) { |
|
216 stack[current].setLabel(string); |
|
217 stack[current].setStackAddress(sp); |
|
218 stack[current].setScript(nullptr); |
|
219 stack[current].setLine(0); |
|
220 } |
|
221 *size = current + 1; |
|
222 } |
|
223 |
|
224 void |
|
225 SPSProfiler::push(const char *string, void *sp, JSScript *script, jsbytecode *pc) |
|
226 { |
|
227 /* these operations cannot be re-ordered, so volatile-ize operations */ |
|
228 volatile ProfileEntry *stack = stack_; |
|
229 volatile uint32_t *size = size_; |
|
230 uint32_t current = *size; |
|
231 |
|
232 JS_ASSERT(installed()); |
|
233 if (current < max_) { |
|
234 stack[current].setLabel(string); |
|
235 stack[current].setStackAddress(sp); |
|
236 stack[current].setScript(script); |
|
237 stack[current].setPC(pc); |
|
238 } |
|
239 *size = current + 1; |
|
240 } |
|
241 |
|
242 void |
|
243 SPSProfiler::pop() |
|
244 { |
|
245 JS_ASSERT(installed()); |
|
246 (*size_)--; |
|
247 JS_ASSERT(*(int*)size_ >= 0); |
|
248 } |
|
249 |
|
250 /* |
|
251 * Serializes the script/function pair into a "descriptive string" which is |
|
252 * allowed to fail. This function cannot trigger a GC because it could finalize |
|
253 * some scripts, resize the hash table of profile strings, and invalidate the |
|
254 * AddPtr held while invoking allocProfileString. |
|
255 */ |
|
256 const char * |
|
257 SPSProfiler::allocProfileString(JSScript *script, JSFunction *maybeFun) |
|
258 { |
|
259 // Note: this profiler string is regexp-matched by |
|
260 // browser/devtools/profiler/cleopatra/js/parserWorker.js. |
|
261 |
|
262 // Determine if the function (if any) has an explicit or guessed name. |
|
263 bool hasAtom = maybeFun && maybeFun->displayAtom(); |
|
264 |
|
265 // Get the function name, if any, and its length. |
|
266 const jschar *atom = nullptr; |
|
267 size_t lenAtom = 0; |
|
268 if (hasAtom) { |
|
269 atom = maybeFun->displayAtom()->charsZ(); |
|
270 lenAtom = maybeFun->displayAtom()->length(); |
|
271 } |
|
272 |
|
273 // Get the script filename, if any, and its length. |
|
274 const char *filename = script->filename(); |
|
275 if (filename == nullptr) |
|
276 filename = "<unknown>"; |
|
277 size_t lenFilename = strlen(filename); |
|
278 |
|
279 // Get the line number and its length as a string. |
|
280 uint64_t lineno = script->lineno(); |
|
281 size_t lenLineno = 1; |
|
282 for (uint64_t i = lineno; i /= 10; lenLineno++); |
|
283 |
|
284 // Determine the required buffer size. |
|
285 size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them. |
|
286 if (hasAtom) |
|
287 len += lenAtom + 3; // +3 for the " (" and ")" it adds. |
|
288 |
|
289 // Allocate the buffer. |
|
290 char *cstr = js_pod_malloc<char>(len + 1); |
|
291 if (cstr == nullptr) |
|
292 return nullptr; |
|
293 |
|
294 // Construct the descriptive string. |
|
295 DebugOnly<size_t> ret; |
|
296 if (hasAtom) |
|
297 ret = JS_snprintf(cstr, len + 1, "%hs (%s:%llu)", atom, filename, lineno); |
|
298 else |
|
299 ret = JS_snprintf(cstr, len + 1, "%s:%llu", filename, lineno); |
|
300 |
|
301 MOZ_ASSERT(ret == len, "Computed length should match actual length!"); |
|
302 |
|
303 return cstr; |
|
304 } |
|
305 |
|
306 SPSEntryMarker::SPSEntryMarker(JSRuntime *rt |
|
307 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
|
308 : profiler(&rt->spsProfiler) |
|
309 { |
|
310 MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
|
311 if (!profiler->installed()) { |
|
312 profiler = nullptr; |
|
313 return; |
|
314 } |
|
315 size_before = *profiler->size_; |
|
316 profiler->pushNoCopy("js::RunScript", this, nullptr, nullptr); |
|
317 } |
|
318 |
|
319 SPSEntryMarker::~SPSEntryMarker() |
|
320 { |
|
321 if (profiler != nullptr) { |
|
322 profiler->pop(); |
|
323 JS_ASSERT(size_before == *profiler->size_); |
|
324 } |
|
325 } |
|
326 |
|
327 JS_FRIEND_API(jsbytecode*) |
|
328 ProfileEntry::pc() const volatile |
|
329 { |
|
330 return idx == NullPCIndex ? nullptr : script()->offsetToPC(idx); |
|
331 } |
|
332 |
|
333 JS_FRIEND_API(void) |
|
334 ProfileEntry::setPC(jsbytecode *pc) volatile |
|
335 { |
|
336 idx = pc == nullptr ? NullPCIndex : script()->pcToOffset(pc); |
|
337 } |
|
338 |
|
339 JS_FRIEND_API(void) |
|
340 js::SetRuntimeProfilingStack(JSRuntime *rt, ProfileEntry *stack, uint32_t *size, uint32_t max) |
|
341 { |
|
342 rt->spsProfiler.setProfilingStack(stack, size, max); |
|
343 } |
|
344 |
|
345 JS_FRIEND_API(void) |
|
346 js::EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled) |
|
347 { |
|
348 rt->spsProfiler.enable(enabled); |
|
349 } |
|
350 |
|
351 JS_FRIEND_API(void) |
|
352 js::RegisterRuntimeProfilingEventMarker(JSRuntime *rt, void (*fn)(const char *)) |
|
353 { |
|
354 JS_ASSERT(rt->spsProfiler.enabled()); |
|
355 rt->spsProfiler.setEventMarker(fn); |
|
356 } |
|
357 |
|
358 JS_FRIEND_API(jsbytecode*) |
|
359 js::ProfilingGetPC(JSRuntime *rt, JSScript *script, void *ip) |
|
360 { |
|
361 return rt->spsProfiler.ipToPC(script, size_t(ip)); |
|
362 } |