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 : #include "vm/GeckoProfiler-inl.h"
8 :
9 : #include "mozilla/DebugOnly.h"
10 :
11 : #include "jsnum.h"
12 : #include "jsprf.h"
13 : #include "jsscript.h"
14 :
15 : #include "jit/BaselineFrame.h"
16 : #include "jit/BaselineJIT.h"
17 : #include "jit/JitcodeMap.h"
18 : #include "jit/JitFrameIterator.h"
19 : #include "jit/JitFrames.h"
20 : #include "vm/StringBuffer.h"
21 :
22 : #include "jsgcinlines.h"
23 :
24 : using namespace js;
25 :
26 : using mozilla::DebugOnly;
27 :
28 4 : GeckoProfiler::GeckoProfiler(JSRuntime* rt)
29 : : rt(rt),
30 : strings(mutexid::GeckoProfilerStrings),
31 : pseudoStack_(nullptr),
32 : slowAssertions(false),
33 : enabled_(false),
34 4 : eventMarker_(nullptr)
35 : {
36 4 : MOZ_ASSERT(rt != nullptr);
37 4 : }
38 :
39 : bool
40 4 : GeckoProfiler::init()
41 : {
42 8 : auto locked = strings.lock();
43 4 : if (!locked->init())
44 0 : return false;
45 :
46 4 : return true;
47 : }
48 :
49 : void
50 4 : GeckoProfiler::setProfilingStack(PseudoStack* pseudoStack)
51 : {
52 4 : MOZ_ASSERT_IF(pseudoStack_, !enabled());
53 4 : MOZ_ASSERT(strings.lock()->initialized());
54 :
55 4 : pseudoStack_ = pseudoStack;
56 4 : }
57 :
58 : void
59 0 : GeckoProfiler::setEventMarker(void (*fn)(const char*))
60 : {
61 0 : eventMarker_ = fn;
62 0 : }
63 :
64 : /* Get a pointer to the top-most profiling frame, given the exit frame pointer. */
65 : static void*
66 0 : GetTopProfilingJitFrame(Activation* act)
67 : {
68 0 : if (!act || !act->isJit())
69 0 : return nullptr;
70 :
71 : // For null exitFrame, there is no previous exit frame, just return.
72 0 : uint8_t* exitFP = act->asJit()->exitFP();
73 0 : if (!exitFP)
74 0 : return nullptr;
75 :
76 0 : jit::JitProfilingFrameIterator iter(exitFP);
77 0 : MOZ_ASSERT(!iter.done());
78 0 : return iter.fp();
79 : }
80 :
81 : bool
82 0 : GeckoProfiler::enable(bool enabled)
83 : {
84 0 : MOZ_ASSERT(installed());
85 :
86 0 : if (enabled_ == enabled)
87 0 : return true;
88 :
89 : // Execution in the runtime must be single threaded if the Gecko profiler
90 : // is enabled. There is only a single profiler stack in the runtime, from
91 : // which entries must be added/removed in a LIFO fashion.
92 0 : JSContext* cx = rt->activeContextFromOwnThread();
93 0 : if (enabled) {
94 0 : if (!rt->beginSingleThreadedExecution(cx))
95 0 : return false;
96 : } else {
97 0 : rt->endSingleThreadedExecution(cx);
98 : }
99 :
100 : /*
101 : * Ensure all future generated code will be instrumented, or that all
102 : * currently instrumented code is discarded
103 : */
104 0 : ReleaseAllJITCode(rt->defaultFreeOp());
105 :
106 : // This function is called when the Gecko profiler makes a new Sampler
107 : // (and thus, a new circular buffer). Set all current entries in the
108 : // JitcodeGlobalTable as expired and reset the buffer generation and lap
109 : // count.
110 0 : if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable())
111 0 : rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(rt);
112 0 : rt->resetProfilerSampleBufferGen();
113 0 : rt->resetProfilerSampleBufferLapCount();
114 :
115 : // Ensure that lastProfilingFrame is null for all threads before 'enabled' becomes true.
116 0 : for (const CooperatingContext& target : rt->cooperatingContexts()) {
117 0 : if (target.context()->jitActivation) {
118 0 : target.context()->jitActivation->setLastProfilingFrame(nullptr);
119 0 : target.context()->jitActivation->setLastProfilingCallSite(nullptr);
120 : }
121 : }
122 :
123 0 : enabled_ = enabled;
124 :
125 : /* Toggle Gecko Profiler-related jumps on baseline jitcode.
126 : * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not
127 : * jitcode for scripts with active frames on the stack. These scripts need to have
128 : * their profiler state toggled so they behave properly.
129 : */
130 0 : jit::ToggleBaselineProfiling(rt, enabled);
131 :
132 : /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on
133 : * stack.
134 : */
135 0 : for (const CooperatingContext& target : rt->cooperatingContexts()) {
136 0 : if (target.context()->jitActivation) {
137 : // Walk through all activations, and set their lastProfilingFrame appropriately.
138 0 : if (enabled) {
139 0 : Activation* act = target.context()->activation();
140 0 : void* lastProfilingFrame = GetTopProfilingJitFrame(act);
141 :
142 0 : jit::JitActivation* jitActivation = target.context()->jitActivation;
143 0 : while (jitActivation) {
144 0 : jitActivation->setLastProfilingFrame(lastProfilingFrame);
145 0 : jitActivation->setLastProfilingCallSite(nullptr);
146 :
147 0 : jitActivation = jitActivation->prevJitActivation();
148 0 : lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
149 : }
150 : } else {
151 0 : jit::JitActivation* jitActivation = target.context()->jitActivation;
152 0 : while (jitActivation) {
153 0 : jitActivation->setLastProfilingFrame(nullptr);
154 0 : jitActivation->setLastProfilingCallSite(nullptr);
155 0 : jitActivation = jitActivation->prevJitActivation();
156 : }
157 : }
158 : }
159 : }
160 :
161 : // WebAssembly code does not need to be released, but profiling string
162 : // labels have to be generated so that they are available during async
163 : // profiling stack iteration.
164 0 : for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
165 0 : c->wasm.ensureProfilingLabels(enabled);
166 :
167 0 : return true;
168 : }
169 :
170 : /* Lookup the string for the function/script, creating one if necessary */
171 : const char*
172 0 : GeckoProfiler::profileString(JSScript* script, JSFunction* maybeFun)
173 : {
174 0 : auto locked = strings.lock();
175 0 : MOZ_ASSERT(locked->initialized());
176 :
177 0 : ProfileStringMap::AddPtr s = locked->lookupForAdd(script);
178 :
179 0 : if (!s) {
180 0 : auto str = allocProfileString(script, maybeFun);
181 0 : if (!str || !locked->add(s, script, mozilla::Move(str)))
182 0 : return nullptr;
183 : }
184 :
185 0 : return s->value().get();
186 : }
187 :
188 : void
189 0 : GeckoProfiler::onScriptFinalized(JSScript* script)
190 : {
191 : /*
192 : * This function is called whenever a script is destroyed, regardless of
193 : * whether profiling has been turned on, so don't invoke a function on an
194 : * invalid hash set. Also, even if profiling was enabled but then turned
195 : * off, we still want to remove the string, so no check of enabled() is
196 : * done.
197 : */
198 0 : auto locked = strings.lock();
199 0 : if (!locked->initialized())
200 0 : return;
201 0 : if (ProfileStringMap::Ptr entry = locked->lookup(script))
202 0 : locked->remove(entry);
203 : }
204 :
205 : void
206 0 : GeckoProfiler::markEvent(const char* event)
207 : {
208 0 : MOZ_ASSERT(enabled());
209 0 : if (eventMarker_) {
210 0 : JS::AutoSuppressGCAnalysis nogc;
211 0 : eventMarker_(event);
212 : }
213 0 : }
214 :
215 : bool
216 0 : GeckoProfiler::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
217 : {
218 0 : const char* dynamicString = profileString(script, maybeFun);
219 0 : if (dynamicString == nullptr) {
220 0 : ReportOutOfMemory(cx);
221 0 : return false;
222 : }
223 :
224 : #ifdef DEBUG
225 : // In debug builds, assert the JS pseudo frames already on the stack
226 : // have a non-null pc. Only look at the top frames to avoid quadratic
227 : // behavior.
228 0 : uint32_t sp = pseudoStack_->stackPointer;
229 0 : if (sp > 0 && sp - 1 < PseudoStack::MaxEntries) {
230 0 : size_t start = (sp > 4) ? sp - 4 : 0;
231 0 : for (size_t i = start; i < sp - 1; i++)
232 0 : MOZ_ASSERT_IF(pseudoStack_->entries[i].isJs(), pseudoStack_->entries[i].pc());
233 : }
234 : #endif
235 :
236 0 : pseudoStack_->pushJsFrame("", dynamicString, script, script->code());
237 0 : return true;
238 : }
239 :
240 : void
241 0 : GeckoProfiler::exit(JSScript* script, JSFunction* maybeFun)
242 : {
243 0 : pseudoStack_->pop();
244 :
245 : #ifdef DEBUG
246 : /* Sanity check to make sure push/pop balanced */
247 0 : uint32_t sp = pseudoStack_->stackPointer;
248 0 : if (sp < PseudoStack::MaxEntries) {
249 0 : const char* dynamicString = profileString(script, maybeFun);
250 : /* Can't fail lookup because we should already be in the set */
251 0 : MOZ_ASSERT(dynamicString);
252 :
253 : // Bug 822041
254 0 : if (!pseudoStack_->entries[sp].isJs()) {
255 0 : fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
256 0 : fprintf(stderr, " entries=%p size=%u/%u\n",
257 0 : (void*) pseudoStack_->entries,
258 0 : uint32_t(pseudoStack_->stackPointer),
259 0 : PseudoStack::MaxEntries);
260 0 : for (int32_t i = sp; i >= 0; i--) {
261 0 : ProfileEntry& entry = pseudoStack_->entries[i];
262 0 : if (entry.isJs())
263 0 : fprintf(stderr, " [%d] JS %s\n", i, entry.dynamicString());
264 : else
265 0 : fprintf(stderr, " [%d] C line %d %s\n", i, entry.line(), entry.dynamicString());
266 : }
267 : }
268 :
269 0 : ProfileEntry& entry = pseudoStack_->entries[sp];
270 0 : MOZ_ASSERT(entry.isJs());
271 0 : MOZ_ASSERT(entry.script() == script);
272 0 : MOZ_ASSERT(strcmp((const char*) entry.dynamicString(), dynamicString) == 0);
273 : }
274 : #endif
275 0 : }
276 :
277 : /*
278 : * Serializes the script/function pair into a "descriptive string" which is
279 : * allowed to fail. This function cannot trigger a GC because it could finalize
280 : * some scripts, resize the hash table of profile strings, and invalidate the
281 : * AddPtr held while invoking allocProfileString.
282 : */
283 : UniqueChars
284 0 : GeckoProfiler::allocProfileString(JSScript* script, JSFunction* maybeFun)
285 : {
286 : // Note: this profiler string is regexp-matched by
287 : // devtools/client/profiler/cleopatra/js/parserWorker.js.
288 :
289 : // Get the function name, if any.
290 0 : JSAtom* atom = maybeFun ? maybeFun->displayAtom() : nullptr;
291 :
292 : // Get the script filename, if any, and its length.
293 0 : const char* filename = script->filename();
294 0 : if (filename == nullptr)
295 0 : filename = "<unknown>";
296 0 : size_t lenFilename = strlen(filename);
297 :
298 : // Get the line number and its length as a string.
299 0 : uint64_t lineno = script->lineno();
300 0 : size_t lenLineno = 1;
301 0 : for (uint64_t i = lineno; i /= 10; lenLineno++);
302 :
303 : // Determine the required buffer size.
304 0 : size_t len = lenFilename + lenLineno + 1; // +1 for the ":" separating them.
305 0 : if (atom) {
306 0 : len += JS::GetDeflatedUTF8StringLength(atom) + 3; // +3 for the " (" and ")" it adds.
307 : }
308 :
309 : // Allocate the buffer.
310 0 : UniqueChars cstr(js_pod_malloc<char>(len + 1));
311 0 : if (!cstr)
312 0 : return nullptr;
313 :
314 : // Construct the descriptive string.
315 0 : DebugOnly<size_t> ret;
316 0 : if (atom) {
317 0 : UniqueChars atomStr = StringToNewUTF8CharsZ(nullptr, *atom);
318 0 : if (!atomStr)
319 0 : return nullptr;
320 :
321 0 : ret = snprintf(cstr.get(), len + 1, "%s (%s:%" PRIu64 ")", atomStr.get(), filename, lineno);
322 : } else {
323 0 : ret = snprintf(cstr.get(), len + 1, "%s:%" PRIu64, filename, lineno);
324 : }
325 :
326 0 : MOZ_ASSERT(ret == len, "Computed length should match actual length!");
327 :
328 0 : return cstr;
329 : }
330 :
331 : void
332 22 : GeckoProfiler::trace(JSTracer* trc)
333 : {
334 22 : if (pseudoStack_) {
335 22 : size_t size = pseudoStack_->stackSize();
336 307 : for (size_t i = 0; i < size; i++)
337 285 : pseudoStack_->entries[i].trace(trc);
338 : }
339 22 : }
340 :
341 : void
342 0 : GeckoProfiler::fixupStringsMapAfterMovingGC()
343 : {
344 0 : auto locked = strings.lock();
345 0 : if (!locked->initialized())
346 0 : return;
347 :
348 0 : for (ProfileStringMap::Enum e(locked.get()); !e.empty(); e.popFront()) {
349 0 : JSScript* script = e.front().key();
350 0 : if (IsForwarded(script)) {
351 0 : script = Forwarded(script);
352 0 : e.rekeyFront(script);
353 : }
354 : }
355 : }
356 :
357 : #ifdef JSGC_HASH_TABLE_CHECKS
358 : void
359 0 : GeckoProfiler::checkStringsMapAfterMovingGC()
360 : {
361 0 : auto locked = strings.lock();
362 0 : if (!locked->initialized())
363 0 : return;
364 :
365 0 : for (auto r = locked->all(); !r.empty(); r.popFront()) {
366 0 : JSScript* script = r.front().key();
367 0 : CheckGCThingAfterMovingGC(script);
368 0 : auto ptr = locked->lookup(script);
369 0 : MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
370 : }
371 : }
372 : #endif
373 :
374 : void
375 285 : ProfileEntry::trace(JSTracer* trc)
376 : {
377 285 : if (isJs()) {
378 101 : JSScript* s = rawScript();
379 101 : TraceNullableRoot(trc, &s, "ProfileEntry script");
380 101 : spOrScript = s;
381 : }
382 285 : }
383 :
384 17386 : GeckoProfilerEntryMarker::GeckoProfilerEntryMarker(JSRuntime* rt,
385 : JSScript* script
386 17386 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
387 17386 : : profiler(&rt->geckoProfiler())
388 : {
389 17386 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
390 17386 : if (!profiler->installed()) {
391 0 : profiler = nullptr;
392 0 : return;
393 : }
394 17386 : spBefore_ = profiler->stackPointer();
395 :
396 : // We want to push a CPP frame so the profiler can correctly order JS and native stacks.
397 : // Only the sp value is important.
398 17386 : profiler->pseudoStack_->pushCppFrame(
399 : /* label = */ "", /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
400 17386 : ProfileEntry::Kind::CPP_MARKER_FOR_JS, ProfileEntry::Category::OTHER);
401 :
402 17386 : profiler->pseudoStack_->pushJsFrame(
403 17386 : "js::RunScript", /* dynamicString = */ nullptr, script, script->code());
404 : }
405 :
406 34758 : GeckoProfilerEntryMarker::~GeckoProfilerEntryMarker()
407 : {
408 17379 : if (profiler == nullptr)
409 0 : return;
410 :
411 17379 : profiler->pseudoStack_->pop(); // the JS frame
412 17379 : profiler->pseudoStack_->pop(); // the BEGIN_PSEUDO_JS frame
413 17379 : MOZ_ASSERT(spBefore_ == profiler->stackPointer());
414 17379 : }
415 :
416 6488 : AutoGeckoProfilerEntry::AutoGeckoProfilerEntry(JSRuntime* rt, const char* label,
417 : ProfileEntry::Category category
418 6488 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
419 6488 : : profiler_(&rt->geckoProfiler())
420 : {
421 6488 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
422 6488 : if (!profiler_->installed()) {
423 0 : profiler_ = nullptr;
424 0 : return;
425 : }
426 6488 : spBefore_ = profiler_->stackPointer();
427 :
428 6488 : profiler_->pseudoStack_->pushCppFrame(
429 : label, /* dynamicString = */ nullptr, /* sp = */ this, /* line = */ 0,
430 6488 : ProfileEntry::Kind::CPP_NORMAL, category);
431 : }
432 :
433 12976 : AutoGeckoProfilerEntry::~AutoGeckoProfilerEntry()
434 : {
435 6488 : if (!profiler_)
436 0 : return;
437 :
438 6488 : profiler_->pseudoStack_->pop();
439 6488 : MOZ_ASSERT(spBefore_ == profiler_->stackPointer());
440 6488 : }
441 :
442 141 : GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSRuntime* rt, bool hasProfilerFrame
443 141 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
444 141 : : profiler(&rt->geckoProfiler())
445 : {
446 141 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
447 141 : if (!hasProfilerFrame || !profiler->enabled()) {
448 141 : profiler = nullptr;
449 282 : return;
450 : }
451 :
452 0 : uint32_t sp = profiler->pseudoStack_->stackPointer;
453 0 : if (sp >= PseudoStack::MaxEntries) {
454 0 : profiler = nullptr;
455 0 : return;
456 : }
457 :
458 0 : spBefore_ = sp;
459 0 : if (sp == 0)
460 0 : return;
461 :
462 0 : ProfileEntry& entry = profiler->pseudoStack_->entries[sp - 1];
463 0 : MOZ_ASSERT(entry.kind() == ProfileEntry::Kind::JS_NORMAL);
464 0 : entry.setKind(ProfileEntry::Kind::JS_OSR);
465 : }
466 :
467 282 : GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker()
468 : {
469 141 : if (profiler == nullptr)
470 141 : return;
471 :
472 0 : uint32_t sp = profiler->stackPointer();
473 0 : MOZ_ASSERT(spBefore_ == sp);
474 0 : if (sp == 0)
475 0 : return;
476 :
477 0 : ProfileEntry& entry = profiler->stack()[sp - 1];
478 0 : MOZ_ASSERT(entry.kind() == ProfileEntry::Kind::JS_OSR);
479 0 : entry.setKind(ProfileEntry::Kind::JS_NORMAL);
480 141 : }
481 :
482 : JS_PUBLIC_API(JSScript*)
483 0 : ProfileEntry::script() const
484 : {
485 0 : MOZ_ASSERT(isJs());
486 0 : auto script = reinterpret_cast<JSScript*>(spOrScript);
487 0 : if (!script)
488 0 : return nullptr;
489 :
490 : // If profiling is supressed then we can't trust the script pointers to be
491 : // valid as they could be in the process of being moved by a compacting GC
492 : // (although it's still OK to get the runtime from them).
493 : //
494 : // We only need to check the active context here, as
495 : // AutoSuppressProfilerSampling prohibits the runtime's active context from
496 : // being changed while it exists.
497 0 : JSContext* cx = script->runtimeFromAnyThread()->activeContext();
498 0 : if (!cx->isProfilerSamplingEnabled())
499 0 : return nullptr;
500 :
501 0 : MOZ_ASSERT(!IsForwarded(script));
502 0 : return script;
503 : }
504 :
505 : JS_FRIEND_API(jsbytecode*)
506 0 : ProfileEntry::pc() const
507 : {
508 0 : MOZ_ASSERT(isJs());
509 0 : if (lineOrPcOffset == NullPCOffset)
510 0 : return nullptr;
511 :
512 0 : JSScript* script = this->script();
513 0 : return script ? script->offsetToPC(lineOrPcOffset) : nullptr;
514 : }
515 :
516 : /* static */ int32_t
517 17386 : ProfileEntry::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
518 17386 : return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
519 : }
520 :
521 : void
522 0 : ProfileEntry::setPC(jsbytecode* pc)
523 : {
524 0 : MOZ_ASSERT(isJs());
525 0 : JSScript* script = this->script();
526 0 : MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
527 0 : lineOrPcOffset = pcToOffset(script, pc);
528 0 : }
529 :
530 : JS_FRIEND_API(void)
531 4 : js::SetContextProfilingStack(JSContext* cx, PseudoStack* pseudoStack)
532 : {
533 4 : cx->runtime()->geckoProfiler().setProfilingStack(pseudoStack);
534 4 : }
535 :
536 : JS_FRIEND_API(void)
537 0 : js::EnableContextProfilingStack(JSContext* cx, bool enabled)
538 : {
539 0 : if (!cx->runtime()->geckoProfiler().enable(enabled))
540 0 : MOZ_CRASH("Execution in this runtime should already be single threaded");
541 0 : }
542 :
543 : JS_FRIEND_API(void)
544 0 : js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*))
545 : {
546 0 : MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
547 0 : cx->runtime()->geckoProfiler().setEventMarker(fn);
548 0 : }
549 :
550 632 : AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx
551 632 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
552 : : cx_(cx),
553 632 : previouslyEnabled_(cx->isProfilerSamplingEnabled()),
554 1264 : prohibitContextChange_(cx->runtime())
555 : {
556 632 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
557 632 : if (previouslyEnabled_)
558 632 : cx_->disableProfilerSampling();
559 632 : }
560 :
561 1264 : AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling()
562 : {
563 632 : if (previouslyEnabled_)
564 632 : cx_->enableProfilerSampling();
565 632 : }
|