Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / vm / GeckoProfiler.cpp
blobdbf1eb9081175a61bc11f2f1d0bb951b869a197b
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/. */
7 #include "vm/GeckoProfiler-inl.h"
9 #include "mozilla/Sprintf.h"
11 #include "gc/GC.h"
12 #include "gc/PublicIterators.h"
13 #include "jit/BaselineJIT.h"
14 #include "jit/JitcodeMap.h"
15 #include "jit/JitRuntime.h"
16 #include "jit/JSJitFrameIter.h"
17 #include "jit/PerfSpewer.h"
18 #include "js/ProfilingStack.h"
19 #include "vm/FrameIter.h" // js::OnlyJSJitFrameIter
20 #include "vm/JitActivation.h"
21 #include "vm/JSScript.h"
23 #include "gc/Marking-inl.h"
24 #include "jit/JSJitFrameIter-inl.h"
26 using namespace js;
28 GeckoProfilerThread::GeckoProfilerThread()
29 : profilingStack_(nullptr), profilingStackIfEnabled_(nullptr) {}
31 GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
32 : rt(rt), slowAssertions(false), enabled_(false), eventMarker_(nullptr) {
33 MOZ_ASSERT(rt != nullptr);
36 void GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack,
37 bool enabled) {
38 profilingStack_ = profilingStack;
39 profilingStackIfEnabled_ = enabled ? profilingStack : nullptr;
42 void GeckoProfilerRuntime::setEventMarker(void (*fn)(const char*,
43 const char*)) {
44 eventMarker_ = fn;
47 // Get a pointer to the top-most profiling frame, given the exit frame pointer.
48 static jit::JitFrameLayout* GetTopProfilingJitFrame(jit::JitActivation* act) {
49 // If there is no exit frame set, just return.
50 if (!act->hasExitFP()) {
51 return nullptr;
54 // Skip wasm frames that might be in the way.
55 OnlyJSJitFrameIter iter(act);
56 if (iter.done()) {
57 return nullptr;
60 // Skip if the activation has no JS frames. This can happen if there's only a
61 // TrampolineNative frame because these are skipped by the profiling frame
62 // iterator.
63 jit::JSJitProfilingFrameIterator jitIter(
64 (jit::CommonFrameLayout*)iter.frame().fp());
65 if (jitIter.done()) {
66 return nullptr;
69 return jitIter.framePtr();
72 void GeckoProfilerRuntime::enable(bool enabled) {
73 JSContext* cx = rt->mainContextFromAnyThread();
74 MOZ_ASSERT(cx->geckoProfiler().infraInstalled());
76 if (enabled_ == enabled) {
77 return;
81 * Ensure all future generated code will be instrumented, or that all
82 * currently instrumented code is discarded
84 ReleaseAllJITCode(rt->gcContext());
86 // This function is called when the Gecko profiler makes a new Sampler
87 // (and thus, a new circular buffer). Set all current entries in the
88 // JitcodeGlobalTable as expired and reset the buffer range start.
89 if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) {
90 rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired();
92 rt->setProfilerSampleBufferRangeStart(0);
94 // Ensure that lastProfilingFrame is null for the main thread.
95 if (cx->jitActivation) {
96 cx->jitActivation->setLastProfilingFrame(nullptr);
97 cx->jitActivation->setLastProfilingCallSite(nullptr);
100 enabled_ = enabled;
102 /* Toggle Gecko Profiler-related jumps on baseline jitcode.
103 * The call to |ReleaseAllJITCode| above will release most baseline jitcode,
104 * but not jitcode for scripts with active frames on the stack. These scripts
105 * need to have their profiler state toggled so they behave properly.
107 jit::ToggleBaselineProfiling(cx, enabled);
109 // Update lastProfilingFrame to point to the top-most JS jit-frame currently
110 // on stack.
111 if (cx->jitActivation) {
112 // Walk through all activations, and set their lastProfilingFrame
113 // appropriately.
114 if (enabled) {
115 jit::JitActivation* jitActivation = cx->jitActivation;
116 while (jitActivation) {
117 auto* lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
118 jitActivation->setLastProfilingFrame(lastProfilingFrame);
119 jitActivation->setLastProfilingCallSite(nullptr);
120 jitActivation = jitActivation->prevJitActivation();
122 } else {
123 jit::JitActivation* jitActivation = cx->jitActivation;
124 while (jitActivation) {
125 jitActivation->setLastProfilingFrame(nullptr);
126 jitActivation->setLastProfilingCallSite(nullptr);
127 jitActivation = jitActivation->prevJitActivation();
132 // WebAssembly code does not need to be released, but profiling string
133 // labels have to be generated so that they are available during async
134 // profiling stack iteration.
135 for (RealmsIter r(rt); !r.done(); r.next()) {
136 r->wasm.ensureProfilingLabels(enabled);
139 #ifdef JS_STRUCTURED_SPEW
140 // Enable the structured spewer if the environment variable is set.
141 if (enabled) {
142 cx->spewer().enableSpewing();
143 } else {
144 cx->spewer().disableSpewing();
146 #endif
149 /* Lookup the string for the function/script, creating one if necessary */
150 const char* GeckoProfilerRuntime::profileString(JSContext* cx,
151 BaseScript* script) {
152 ProfileStringMap::AddPtr s = strings().lookupForAdd(script);
154 if (!s) {
155 UniqueChars str = allocProfileString(cx, script);
156 if (!str) {
157 return nullptr;
159 MOZ_ASSERT(script->hasBytecode());
160 if (!strings().add(s, script, std::move(str))) {
161 ReportOutOfMemory(cx);
162 return nullptr;
166 return s->value().get();
169 void GeckoProfilerRuntime::onScriptFinalized(BaseScript* script) {
171 * This function is called whenever a script is destroyed, regardless of
172 * whether profiling has been turned on, so don't invoke a function on an
173 * invalid hash set. Also, even if profiling was enabled but then turned
174 * off, we still want to remove the string, so no check of enabled() is
175 * done.
177 if (ProfileStringMap::Ptr entry = strings().lookup(script)) {
178 strings().remove(entry);
182 void GeckoProfilerRuntime::markEvent(const char* event, const char* details) {
183 MOZ_ASSERT(enabled());
184 if (eventMarker_) {
185 JS::AutoSuppressGCAnalysis nogc;
186 eventMarker_(event, details);
190 bool GeckoProfilerThread::enter(JSContext* cx, JSScript* script) {
191 const char* dynamicString =
192 cx->runtime()->geckoProfiler().profileString(cx, script);
193 if (dynamicString == nullptr) {
194 return false;
197 #ifdef DEBUG
198 // In debug builds, assert the JS profiling stack frames already on the
199 // stack have a non-null pc. Only look at the top frames to avoid quadratic
200 // behavior.
201 uint32_t sp = profilingStack_->stackPointer;
202 if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) {
203 size_t start = (sp > 4) ? sp - 4 : 0;
204 for (size_t i = start; i < sp - 1; i++) {
205 MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(),
206 profilingStack_->frames[i].pc());
209 #endif
211 profilingStack_->pushJsFrame(
212 "", dynamicString, script, script->code(),
213 script->realm()->creationOptions().profilerRealmID());
214 return true;
217 void GeckoProfilerThread::exit(JSContext* cx, JSScript* script) {
218 profilingStack_->pop();
220 #ifdef DEBUG
221 /* Sanity check to make sure push/pop balanced */
222 uint32_t sp = profilingStack_->stackPointer;
223 if (sp < profilingStack_->stackCapacity()) {
224 JSRuntime* rt = script->runtimeFromMainThread();
225 const char* dynamicString = rt->geckoProfiler().profileString(cx, script);
226 /* Can't fail lookup because we should already be in the set */
227 MOZ_ASSERT(dynamicString);
229 // Bug 822041
230 if (!profilingStack_->frames[sp].isJsFrame()) {
231 fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
232 fprintf(stderr, " frames=%p size=%u/%u\n", (void*)profilingStack_->frames,
233 uint32_t(profilingStack_->stackPointer),
234 profilingStack_->stackCapacity());
235 for (int32_t i = sp; i >= 0; i--) {
236 ProfilingStackFrame& frame = profilingStack_->frames[i];
237 if (frame.isJsFrame()) {
238 fprintf(stderr, " [%d] JS %s\n", i, frame.dynamicString());
239 } else {
240 fprintf(stderr, " [%d] Label %s\n", i, frame.dynamicString());
245 ProfilingStackFrame& frame = profilingStack_->frames[sp];
246 MOZ_ASSERT(frame.isJsFrame());
247 MOZ_ASSERT(frame.script() == script);
248 MOZ_ASSERT(strcmp((const char*)frame.dynamicString(), dynamicString) == 0);
250 #endif
254 * Serializes the script/function pair into a "descriptive string" which is
255 * allowed to fail. This function cannot trigger a GC because it could finalize
256 * some scripts, resize the hash table of profile strings, and invalidate the
257 * AddPtr held while invoking allocProfileString.
259 /* static */
260 UniqueChars GeckoProfilerRuntime::allocProfileString(JSContext* cx,
261 BaseScript* script) {
262 // Note: this profiler string is regexp-matched by
263 // devtools/client/profiler/cleopatra/js/parserWorker.js.
265 // If the script has a function, try calculating its name.
266 bool hasName = false;
267 size_t nameLength = 0;
268 UniqueChars nameStr;
269 JSFunction* func = script->function();
270 if (func && func->fullDisplayAtom()) {
271 nameStr = StringToNewUTF8CharsZ(cx, *func->fullDisplayAtom());
272 if (!nameStr) {
273 return nullptr;
276 nameLength = strlen(nameStr.get());
277 hasName = true;
280 // Calculate filename length. We cap this to a reasonable limit to avoid
281 // performance impact of strlen/alloc/memcpy.
282 constexpr size_t MaxFilenameLength = 200;
283 const char* filenameStr = script->filename() ? script->filename() : "(null)";
284 size_t filenameLength = js_strnlen(filenameStr, MaxFilenameLength);
286 // Calculate line + column length.
287 bool hasLineAndColumn = false;
288 size_t lineAndColumnLength = 0;
289 char lineAndColumnStr[30];
290 if (hasName || script->isFunction() || script->isForEval()) {
291 lineAndColumnLength =
292 SprintfLiteral(lineAndColumnStr, "%u:%u", script->lineno(),
293 script->column().oneOriginValue());
294 hasLineAndColumn = true;
297 // Full profile string for scripts with functions is:
298 // FuncName (FileName:Lineno:Column)
299 // Full profile string for scripts without functions is:
300 // FileName:Lineno:Column
301 // Full profile string for scripts without functions and without lines is:
302 // FileName
304 // Calculate full string length.
305 size_t fullLength = 0;
306 if (hasName) {
307 MOZ_ASSERT(hasLineAndColumn);
308 fullLength = nameLength + 2 + filenameLength + 1 + lineAndColumnLength + 1;
309 } else if (hasLineAndColumn) {
310 fullLength = filenameLength + 1 + lineAndColumnLength;
311 } else {
312 fullLength = filenameLength;
315 // Allocate string.
316 UniqueChars str(cx->pod_malloc<char>(fullLength + 1));
317 if (!str) {
318 return nullptr;
321 size_t cur = 0;
323 // Fill string with function name if needed.
324 if (hasName) {
325 memcpy(str.get() + cur, nameStr.get(), nameLength);
326 cur += nameLength;
327 str[cur++] = ' ';
328 str[cur++] = '(';
331 // Fill string with filename chars.
332 memcpy(str.get() + cur, filenameStr, filenameLength);
333 cur += filenameLength;
335 // Fill line + column chars.
336 if (hasLineAndColumn) {
337 str[cur++] = ':';
338 memcpy(str.get() + cur, lineAndColumnStr, lineAndColumnLength);
339 cur += lineAndColumnLength;
342 // Terminal ')' if necessary.
343 if (hasName) {
344 str[cur++] = ')';
347 MOZ_ASSERT(cur == fullLength);
348 str[cur] = 0;
350 return str;
353 void GeckoProfilerThread::trace(JSTracer* trc) {
354 if (profilingStack_) {
355 size_t size = profilingStack_->stackSize();
356 for (size_t i = 0; i < size; i++) {
357 profilingStack_->frames[i].trace(trc);
362 void GeckoProfilerRuntime::fixupStringsMapAfterMovingGC() {
363 for (ProfileStringMap::Enum e(strings()); !e.empty(); e.popFront()) {
364 BaseScript* script = e.front().key();
365 if (IsForwarded(script)) {
366 script = Forwarded(script);
367 e.rekeyFront(script);
372 #ifdef JSGC_HASH_TABLE_CHECKS
373 void GeckoProfilerRuntime::checkStringsMapAfterMovingGC() {
374 for (auto r = strings().all(); !r.empty(); r.popFront()) {
375 BaseScript* script = r.front().key();
376 CheckGCThingAfterMovingGC(script);
377 auto ptr = strings().lookup(script);
378 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
381 #endif
383 void ProfilingStackFrame::trace(JSTracer* trc) {
384 if (isJsFrame()) {
385 JSScript* s = rawScript();
386 TraceNullableRoot(trc, &s, "ProfilingStackFrame script");
387 spOrScript = s;
391 GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(
392 JSContext* cx, bool hasProfilerFrame)
393 : profiler(&cx->geckoProfiler()) {
394 if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) {
395 profiler = nullptr;
396 return;
399 uint32_t sp = profiler->profilingStack_->stackPointer;
400 if (sp >= profiler->profilingStack_->stackCapacity()) {
401 profiler = nullptr;
402 return;
405 spBefore_ = sp;
406 if (sp == 0) {
407 return;
410 ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1];
411 MOZ_ASSERT(!frame.isOSRFrame());
412 frame.setIsOSRFrame(true);
415 GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() {
416 if (profiler == nullptr) {
417 return;
420 uint32_t sp = profiler->stackPointer();
421 MOZ_ASSERT(spBefore_ == sp);
422 if (sp == 0) {
423 return;
426 ProfilingStackFrame& frame = profiler->stack()[sp - 1];
427 MOZ_ASSERT(frame.isOSRFrame());
428 frame.setIsOSRFrame(false);
431 JS_PUBLIC_API JSScript* ProfilingStackFrame::script() const {
432 MOZ_ASSERT(isJsFrame());
433 auto* script = reinterpret_cast<JSScript*>(spOrScript.operator void*());
434 if (!script) {
435 return nullptr;
438 // If profiling is supressed then we can't trust the script pointers to be
439 // valid as they could be in the process of being moved by a compacting GC
440 // (although it's still OK to get the runtime from them).
441 JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread();
442 if (!cx->isProfilerSamplingEnabled()) {
443 return nullptr;
446 MOZ_ASSERT(!IsForwarded(script));
447 return script;
450 JS_PUBLIC_API JSFunction* ProfilingStackFrame::function() const {
451 JSScript* script = this->script();
452 return script ? script->function() : nullptr;
455 JS_PUBLIC_API jsbytecode* ProfilingStackFrame::pc() const {
456 MOZ_ASSERT(isJsFrame());
457 if (pcOffsetIfJS_ == NullPCOffset) {
458 return nullptr;
461 JSScript* script = this->script();
462 return script ? script->offsetToPC(pcOffsetIfJS_) : nullptr;
465 /* static */
466 int32_t ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
467 return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
470 void ProfilingStackFrame::setPC(jsbytecode* pc) {
471 MOZ_ASSERT(isJsFrame());
472 JSScript* script = this->script();
473 MOZ_ASSERT(
474 script); // This should not be called while profiling is suppressed.
475 pcOffsetIfJS_ = pcToOffset(script, pc);
478 JS_PUBLIC_API void js::SetContextProfilingStack(
479 JSContext* cx, ProfilingStack* profilingStack) {
480 cx->geckoProfiler().setProfilingStack(
481 profilingStack, cx->runtime()->geckoProfiler().enabled());
484 JS_PUBLIC_API void js::EnableContextProfilingStack(JSContext* cx,
485 bool enabled) {
486 cx->geckoProfiler().enable(enabled);
487 cx->runtime()->geckoProfiler().enable(enabled);
490 JS_PUBLIC_API void js::RegisterContextProfilingEventMarker(
491 JSContext* cx, void (*fn)(const char*, const char*)) {
492 MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
493 cx->runtime()->geckoProfiler().setEventMarker(fn);
496 AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx)
497 : cx_(cx), previouslyEnabled_(cx->isProfilerSamplingEnabled()) {
498 if (previouslyEnabled_) {
499 cx_->disableProfilerSampling();
503 AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() {
504 if (previouslyEnabled_) {
505 cx_->enableProfilerSampling();
509 namespace JS {
511 // clang-format off
513 // ProfilingSubcategory_X:
514 // One enum for each category X, listing that category's subcategories. This
515 // allows the sProfilingCategoryInfo macro construction below to look up a
516 // per-category index for a subcategory.
517 #define SUBCATEGORY_ENUMS_BEGIN_CATEGORY(name, labelAsString, color) \
518 enum class ProfilingSubcategory_##name : uint32_t {
519 #define SUBCATEGORY_ENUMS_SUBCATEGORY(category, name, labelAsString) \
520 name,
521 #define SUBCATEGORY_ENUMS_END_CATEGORY \
523 MOZ_PROFILING_CATEGORY_LIST(SUBCATEGORY_ENUMS_BEGIN_CATEGORY,
524 SUBCATEGORY_ENUMS_SUBCATEGORY,
525 SUBCATEGORY_ENUMS_END_CATEGORY)
526 #undef SUBCATEGORY_ENUMS_BEGIN_CATEGORY
527 #undef SUBCATEGORY_ENUMS_SUBCATEGORY
528 #undef SUBCATEGORY_ENUMS_END_CATEGORY
530 // sProfilingCategoryPairInfo:
531 // A list of ProfilingCategoryPairInfos with the same order as
532 // ProfilingCategoryPair, which can be used to map a ProfilingCategoryPair to
533 // its information.
534 #define CATEGORY_INFO_BEGIN_CATEGORY(name, labelAsString, color)
535 #define CATEGORY_INFO_SUBCATEGORY(category, name, labelAsString) \
536 {ProfilingCategory::category, \
537 uint32_t(ProfilingSubcategory_##category::name), labelAsString},
538 #define CATEGORY_INFO_END_CATEGORY
539 const ProfilingCategoryPairInfo sProfilingCategoryPairInfo[] = {
540 MOZ_PROFILING_CATEGORY_LIST(CATEGORY_INFO_BEGIN_CATEGORY,
541 CATEGORY_INFO_SUBCATEGORY,
542 CATEGORY_INFO_END_CATEGORY)
544 #undef CATEGORY_INFO_BEGIN_CATEGORY
545 #undef CATEGORY_INFO_SUBCATEGORY
546 #undef CATEGORY_INFO_END_CATEGORY
548 // clang-format on
550 JS_PUBLIC_API const ProfilingCategoryPairInfo& GetProfilingCategoryPairInfo(
551 ProfilingCategoryPair aCategoryPair) {
552 static_assert(
553 MOZ_ARRAY_LENGTH(sProfilingCategoryPairInfo) ==
554 uint32_t(ProfilingCategoryPair::COUNT),
555 "sProfilingCategoryPairInfo and ProfilingCategory need to have the "
556 "same order and the same length");
558 uint32_t categoryPairIndex = uint32_t(aCategoryPair);
559 MOZ_RELEASE_ASSERT(categoryPairIndex <=
560 uint32_t(ProfilingCategoryPair::LAST));
561 return sProfilingCategoryPairInfo[categoryPairIndex];
564 } // namespace JS