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"
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"
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
,
38 profilingStack_
= profilingStack
;
39 profilingStackIfEnabled_
= enabled
? profilingStack
: nullptr;
42 void GeckoProfilerRuntime::setEventMarker(void (*fn
)(const char*,
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()) {
54 // Skip wasm frames that might be in the way.
55 OnlyJSJitFrameIter
iter(act
);
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
63 jit::JSJitProfilingFrameIterator
jitIter(
64 (jit::CommonFrameLayout
*)iter
.frame().fp());
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
) {
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);
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
111 if (cx
->jitActivation
) {
112 // Walk through all activations, and set their lastProfilingFrame
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();
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.
142 cx
->spewer().enableSpewing();
144 cx
->spewer().disableSpewing();
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
);
155 UniqueChars str
= allocProfileString(cx
, script
);
159 MOZ_ASSERT(script
->hasBytecode());
160 if (!strings().add(s
, script
, std::move(str
))) {
161 ReportOutOfMemory(cx
);
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
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());
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) {
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
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());
211 profilingStack_
->pushJsFrame(
212 "", dynamicString
, script
, script
->code(),
213 script
->realm()->creationOptions().profilerRealmID());
217 void GeckoProfilerThread::exit(JSContext
* cx
, JSScript
* script
) {
218 profilingStack_
->pop();
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
);
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());
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);
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.
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;
269 JSFunction
* func
= script
->function();
270 if (func
&& func
->fullDisplayAtom()) {
271 nameStr
= StringToNewUTF8CharsZ(cx
, *func
->fullDisplayAtom());
276 nameLength
= strlen(nameStr
.get());
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:
304 // Calculate full string length.
305 size_t fullLength
= 0;
307 MOZ_ASSERT(hasLineAndColumn
);
308 fullLength
= nameLength
+ 2 + filenameLength
+ 1 + lineAndColumnLength
+ 1;
309 } else if (hasLineAndColumn
) {
310 fullLength
= filenameLength
+ 1 + lineAndColumnLength
;
312 fullLength
= filenameLength
;
316 UniqueChars
str(cx
->pod_malloc
<char>(fullLength
+ 1));
323 // Fill string with function name if needed.
325 memcpy(str
.get() + cur
, nameStr
.get(), nameLength
);
331 // Fill string with filename chars.
332 memcpy(str
.get() + cur
, filenameStr
, filenameLength
);
333 cur
+= filenameLength
;
335 // Fill line + column chars.
336 if (hasLineAndColumn
) {
338 memcpy(str
.get() + cur
, lineAndColumnStr
, lineAndColumnLength
);
339 cur
+= lineAndColumnLength
;
342 // Terminal ')' if necessary.
347 MOZ_ASSERT(cur
== fullLength
);
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());
383 void ProfilingStackFrame::trace(JSTracer
* trc
) {
385 JSScript
* s
= rawScript();
386 TraceNullableRoot(trc
, &s
, "ProfilingStackFrame script");
391 GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(
392 JSContext
* cx
, bool hasProfilerFrame
)
393 : profiler(&cx
->geckoProfiler()) {
394 if (!hasProfilerFrame
|| !cx
->runtime()->geckoProfiler().enabled()) {
399 uint32_t sp
= profiler
->profilingStack_
->stackPointer
;
400 if (sp
>= profiler
->profilingStack_
->stackCapacity()) {
410 ProfilingStackFrame
& frame
= profiler
->profilingStack_
->frames
[sp
- 1];
411 MOZ_ASSERT(!frame
.isOSRFrame());
412 frame
.setIsOSRFrame(true);
415 GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() {
416 if (profiler
== nullptr) {
420 uint32_t sp
= profiler
->stackPointer();
421 MOZ_ASSERT(spBefore_
== sp
);
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*());
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()) {
446 MOZ_ASSERT(!IsForwarded(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
) {
461 JSScript
* script
= this->script();
462 return script
? script
->offsetToPC(pcOffsetIfJS_
) : nullptr;
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();
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
,
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();
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) \
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
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
550 JS_PUBLIC_API
const ProfilingCategoryPairInfo
& GetProfilingCategoryPairInfo(
551 ProfilingCategoryPair aCategoryPair
) {
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
];