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/. */
6 
7 #include "vm/TraceLogging.h"
8 
9 #include "mozilla/EndianUtils.h"
10 #include "mozilla/MemoryReporting.h"
11 
12 #include <algorithm>
13 #include <string.h>
14 #include <utility>
15 
16 #include "jit/BaselineJIT.h"
17 #include "jit/JitSpewer.h"
18 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
19 #include "js/Printf.h"
20 #include "js/TraceLoggerAPI.h"
21 #include "threading/LockGuard.h"
22 #include "util/GetPidProvider.h"  // getpid()
23 #include "util/Text.h"
24 #include "vm/Activation.h"  // js::ActivationIterator
25 #include "vm/FrameIter.h"   // js::JitFrameIter
26 #include "vm/JSContext.h"
27 #include "vm/JSScript.h"
28 #include "vm/Runtime.h"
29 #include "vm/Time.h"
30 #include "vm/TraceLoggingGraph.h"
31 
32 using namespace js;
33 
34 static TraceLoggerThreadState* traceLoggerState = nullptr;
35 
getTraceLoggerSupported()36 static bool getTraceLoggerSupported() {
37   char* str = getenv("JS_TRACE_LOGGING");
38 
39   if (!str) {
40     // Default to unsupported.
41     return false;
42   }
43 
44   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0 ||
45       strcmp(str, "0") == 0) {
46     return false;
47   }
48 
49   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0 ||
50       strcmp(str, "1") == 0) {
51     fprintf(stderr, "Warning: TraceLogger disabled temporarily, bug 1565788\n");
52     return false;
53   }
54 
55   fprintf(stderr, "Warning: I didn't understand JS_TRACE_LOGGING=\"%s\"\n",
56           str);
57   return false;
58 }
59 
SizeOfTraceLogState(mozilla::MallocSizeOf mallocSizeOf)60 size_t js::SizeOfTraceLogState(mozilla::MallocSizeOf mallocSizeOf) {
61   return traceLoggerState ? traceLoggerState->sizeOfIncludingThis(mallocSizeOf)
62                           : 0;
63 }
64 
ResetTraceLogger()65 void js::ResetTraceLogger() {
66   if (!traceLoggerState) {
67     return;
68   }
69 
70   traceLoggerState->clear();
71 }
72 
DestroyTraceLoggerThreadState()73 void js::DestroyTraceLoggerThreadState() {
74   if (traceLoggerState) {
75     js_delete(traceLoggerState);
76     traceLoggerState = nullptr;
77   }
78 }
79 
80 #ifdef DEBUG
AssertCurrentThreadOwnsTraceLoggerThreadStateLock()81 void js::AssertCurrentThreadOwnsTraceLoggerThreadStateLock() {
82   if (traceLoggerState) {
83     traceLoggerState->lock.assertOwnedByCurrentThread();
84   }
85 }
86 #endif
87 
DestroyTraceLogger(TraceLoggerThread * logger)88 void js::DestroyTraceLogger(TraceLoggerThread* logger) {
89   MOZ_ASSERT(traceLoggerState);
90   traceLoggerState->destroyLogger(logger);
91 }
92 
init()93 bool TraceLoggerThread::init() {
94   if (!events_.init()) {
95     return false;
96   }
97 
98   // Minimum amount of capacity needed for operation to allow flushing.
99   // Flushing requires space for the actual event and two spaces to log the
100   // start and stop of flushing.
101   if (!events_.ensureSpaceBeforeAdd(3)) {
102     return false;
103   }
104 
105   char* buffer = js_pod_malloc<char>(32);
106   js::ThisThread::GetName(buffer, 32);
107   threadName_.reset(buffer);
108 
109   return true;
110 }
111 
initGraph()112 void TraceLoggerThread::initGraph() {
113   // Create a graph. I don't like this is called reset, but it locks the
114   // graph into the UniquePtr. So it gets deleted when TraceLoggerThread
115   // is destructed.
116   graph_.reset(js_new<TraceLoggerGraph>());
117   if (!graph_.get()) {
118     return;
119   }
120 
121   MOZ_ASSERT(traceLoggerState);
122   bool graphFile = traceLoggerState->isGraphFileEnabled();
123   double delta =
124       traceLoggerState->getTimeStampOffset(mozilla::TimeStamp::Now());
125   uint64_t start = static_cast<uint64_t>(delta);
126   if (!graph_->init(start, graphFile)) {
127     graph_ = nullptr;
128     return;
129   }
130 
131   if (graphFile) {
132     // Report the textIds to the graph.
133     for (uint32_t i = 0; i < TraceLogger_TreeItemEnd; i++) {
134       TraceLoggerTextId id = TraceLoggerTextId(i);
135       graph_->addTextId(i, TLTextIdString(id));
136     }
137     graph_->addTextId(TraceLogger_TreeItemEnd, "TraceLogger internal");
138     for (uint32_t i = TraceLogger_TreeItemEnd + 1; i < TraceLogger_Last; i++) {
139       TraceLoggerTextId id = TraceLoggerTextId(i);
140       graph_->addTextId(i, TLTextIdString(id));
141     }
142   }
143 }
144 
disableAllTextIds()145 void TraceLoggerThreadState::disableAllTextIds() {
146   for (uint32_t i = 1; i < TraceLogger_Last; i++) {
147     enabledTextIds[i] = false;
148   }
149 }
150 
enableTextIdsForProfiler()151 void TraceLoggerThreadState::enableTextIdsForProfiler() {
152   enableDefaultLogging();
153 }
154 
disableTextIdsForProfiler()155 void TraceLoggerThreadState::disableTextIdsForProfiler() {
156   disableAllTextIds();
157   // We have to keep the Baseline and IonMonkey id's alive because they control
158   // whether the jitted codegen has tracelogger start & stop events builtin.
159   // Otherwise, we end up in situations when some jitted code that was created
160   // before the profiler was even started ends up not starting and stoping any
161   // events.  The TraceLogger_Engine stop events can accidentally stop the wrong
162   // event in this case, and then it's no longer possible to build a graph.
163   enabledTextIds[TraceLogger_Engine] = true;
164   enabledTextIds[TraceLogger_Interpreter] = true;
165   enabledTextIds[TraceLogger_Baseline] = true;
166   enabledTextIds[TraceLogger_IonMonkey] = true;
167 }
168 
enableDefaultLogging()169 void TraceLoggerThreadState::enableDefaultLogging() {
170   enabledTextIds[TraceLogger_AnnotateScripts] = true;
171   enabledTextIds[TraceLogger_Bailout] = true;
172   enabledTextIds[TraceLogger_Baseline] = true;
173   enabledTextIds[TraceLogger_BaselineCompilation] = true;
174   enabledTextIds[TraceLogger_GC] = true;
175   enabledTextIds[TraceLogger_GCAllocation] = true;
176   enabledTextIds[TraceLogger_GCSweeping] = true;
177   enabledTextIds[TraceLogger_Interpreter] = true;
178   enabledTextIds[TraceLogger_IonAnalysis] = true;
179   enabledTextIds[TraceLogger_IonCompilation] = true;
180   enabledTextIds[TraceLogger_IonLinking] = true;
181   enabledTextIds[TraceLogger_IonMonkey] = true;
182   enabledTextIds[TraceLogger_MinorGC] = true;
183   enabledTextIds[TraceLogger_Frontend] = true;
184   enabledTextIds[TraceLogger_ParsingFull] = true;
185   enabledTextIds[TraceLogger_ParsingSyntax] = true;
186   enabledTextIds[TraceLogger_BytecodeEmission] = true;
187   enabledTextIds[TraceLogger_IrregexpCompile] = true;
188   enabledTextIds[TraceLogger_IrregexpExecute] = true;
189   enabledTextIds[TraceLogger_Scripts] = true;
190   enabledTextIds[TraceLogger_Engine] = true;
191   enabledTextIds[TraceLogger_WasmCompilation] = true;
192   enabledTextIds[TraceLogger_Invalidation] = true;
193 }
194 
enableIonLogging()195 void TraceLoggerThreadState::enableIonLogging() {
196   enabledTextIds[TraceLogger_IonCompilation] = true;
197   enabledTextIds[TraceLogger_IonLinking] = true;
198   enabledTextIds[TraceLogger_PruneUnusedBranches] = true;
199   enabledTextIds[TraceLogger_FoldTests] = true;
200   enabledTextIds[TraceLogger_SplitCriticalEdges] = true;
201   enabledTextIds[TraceLogger_RenumberBlocks] = true;
202   enabledTextIds[TraceLogger_ScalarReplacement] = true;
203   enabledTextIds[TraceLogger_DominatorTree] = true;
204   enabledTextIds[TraceLogger_PhiAnalysis] = true;
205   enabledTextIds[TraceLogger_MakeLoopsContiguous] = true;
206   enabledTextIds[TraceLogger_ApplyTypes] = true;
207   enabledTextIds[TraceLogger_EagerSimdUnbox] = true;
208   enabledTextIds[TraceLogger_AliasAnalysis] = true;
209   enabledTextIds[TraceLogger_GVN] = true;
210   enabledTextIds[TraceLogger_LICM] = true;
211   enabledTextIds[TraceLogger_RangeAnalysis] = true;
212   enabledTextIds[TraceLogger_LoopUnrolling] = true;
213   enabledTextIds[TraceLogger_FoldLinearArithConstants] = true;
214   enabledTextIds[TraceLogger_EffectiveAddressAnalysis] = true;
215   enabledTextIds[TraceLogger_AlignmentMaskAnalysis] = true;
216   enabledTextIds[TraceLogger_EliminateDeadCode] = true;
217   enabledTextIds[TraceLogger_ReorderInstructions] = true;
218   enabledTextIds[TraceLogger_EdgeCaseAnalysis] = true;
219   enabledTextIds[TraceLogger_EliminateRedundantChecks] = true;
220   enabledTextIds[TraceLogger_AddKeepAliveInstructions] = true;
221   enabledTextIds[TraceLogger_GenerateLIR] = true;
222   enabledTextIds[TraceLogger_RegisterAllocation] = true;
223   enabledTextIds[TraceLogger_GenerateCode] = true;
224   enabledTextIds[TraceLogger_Scripts] = true;
225 }
226 
enableFrontendLogging()227 void TraceLoggerThreadState::enableFrontendLogging() {
228   enabledTextIds[TraceLogger_Frontend] = true;
229   enabledTextIds[TraceLogger_ParsingFull] = true;
230   enabledTextIds[TraceLogger_ParsingSyntax] = true;
231   enabledTextIds[TraceLogger_BytecodeEmission] = true;
232   enabledTextIds[TraceLogger_BytecodeFoldConstants] = true;
233   enabledTextIds[TraceLogger_BytecodeNameFunctions] = true;
234 }
235 
~TraceLoggerThread()236 TraceLoggerThread::~TraceLoggerThread() {
237   if (graph_.get()) {
238     if (!failed_) {
239       graph_->log(events_, traceLoggerState->startTime);
240     }
241     graph_ = nullptr;
242   }
243 }
244 
enable()245 bool TraceLoggerThread::enable() {
246   if (enabled_ > 0) {
247     enabled_++;
248     return true;
249   }
250 
251   if (failed_) {
252     return false;
253   }
254 
255   enabled_ = 1;
256   logTimestamp(TraceLogger_Enable);
257 
258   return true;
259 }
260 
fail(JSContext * cx,const char * error)261 bool TraceLoggerThread::fail(JSContext* cx, const char* error) {
262   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
263                             JSMSG_TRACELOGGER_ENABLE_FAIL, error);
264   failed_ = true;
265   enabled_ = 0;
266 
267   return false;
268 }
269 
silentFail(const char * error)270 void TraceLoggerThread::silentFail(const char* error) {
271   traceLoggerState->maybeSpewError(error);
272   failed_ = true;
273   enabled_ = 0;
274 }
275 
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const276 size_t TraceLoggerThread::sizeOfExcludingThis(
277     mozilla::MallocSizeOf mallocSizeOf) const {
278   size_t size = 0;
279 #ifdef DEBUG
280   size += graphStack_.sizeOfExcludingThis(mallocSizeOf);
281 #endif
282   size += events_.sizeOfExcludingThis(mallocSizeOf);
283   if (graph_.get()) {
284     size += graph_->sizeOfIncludingThis(mallocSizeOf);
285   }
286   return size;
287 }
288 
sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const289 size_t TraceLoggerThread::sizeOfIncludingThis(
290     mozilla::MallocSizeOf mallocSizeOf) const {
291   return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
292 }
293 
enable(JSContext * cx)294 bool TraceLoggerThread::enable(JSContext* cx) {
295   using namespace js::jit;
296 
297   if (!enable()) {
298     return fail(cx, "internal error");
299   }
300 
301   if (enabled_ == 1) {
302     // Get the top Activation to log the top script/pc (No inlined frames).
303     ActivationIterator iter(cx);
304     Activation* act = iter.activation();
305 
306     if (!act) {
307       return fail(cx, "internal error");
308     }
309 
310     JSScript* script = nullptr;
311     int32_t engine = 0;
312 
313     if (act->isJit()) {
314       JitFrameIter frame(iter->asJit());
315 
316       while (!frame.done()) {
317         if (frame.isWasm()) {
318           JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
319                                     JSMSG_TRACELOGGER_ENABLE_FAIL,
320                                     "not yet supported in wasm code");
321           return false;
322         }
323         if (frame.asJSJit().isScripted()) {
324           break;
325         }
326         ++frame;
327       }
328 
329       MOZ_ASSERT(!frame.done());
330 
331       const JSJitFrameIter& jitFrame = frame.asJSJit();
332       MOZ_ASSERT(jitFrame.isIonJS() || jitFrame.isBaselineJS());
333 
334       script = jitFrame.script();
335       engine =
336           jitFrame.isIonJS() ? TraceLogger_IonMonkey : TraceLogger_Baseline;
337     } else {
338       MOZ_ASSERT(act->isInterpreter());
339       InterpreterFrame* fp = act->asInterpreter()->current();
340       MOZ_ASSERT(!fp->runningInJit());
341 
342       script = fp->script();
343       engine = TraceLogger_Interpreter;
344     }
345     if (script->compartment() != cx->compartment()) {
346       return fail(cx, "compartment mismatch");
347     }
348 
349     TraceLoggerEvent event(TraceLogger_Scripts, script);
350     startEvent(event);
351     startEvent(engine);
352   }
353 
354   return true;
355 }
356 
disable(bool force,const char * error)357 bool TraceLoggerThread::disable(bool force, const char* error) {
358   if (failed_) {
359     MOZ_ASSERT(enabled_ == 0);
360     return false;
361   }
362 
363   if (enabled_ == 0) {
364     return true;
365   }
366 
367   if (enabled_ > 1 && !force) {
368     enabled_--;
369     return true;
370   }
371 
372   if (force) {
373     traceLoggerState->maybeSpewError(error);
374   }
375 
376   logTimestamp(TraceLogger_Disable);
377   enabled_ = 0;
378 
379   return true;
380 }
381 
maybeEventText(uint32_t id)382 const char* TraceLoggerThread::maybeEventText(uint32_t id) {
383   if (TLTextIdIsEnumEvent(id)) {
384     return TLTextIdString(static_cast<TraceLoggerTextId>(id));
385   }
386   return traceLoggerState->maybeEventText(id);
387 }
388 
getPayload(uint32_t id)389 TraceLoggerEventPayload* TraceLoggerThreadState::getPayload(uint32_t id) {
390   if (TLTextIdIsEnumEvent(id)) {
391     return nullptr;
392   }
393 
394   TextIdToPayloadMap::Ptr p = textIdPayloads.lookup(id);
395   if (!p) {
396     return nullptr;
397   }
398 
399   p->value()->use();
400   return p->value();
401 }
402 
maybeEventText(uint32_t id)403 const char* TraceLoggerThreadState::maybeEventText(uint32_t id) {
404   LockGuard<Mutex> guard(lock);
405 
406   TextIdToPayloadMap::Ptr p = textIdPayloads.lookup(id);
407   if (!p) {
408     return nullptr;
409   }
410 
411   uint32_t dictId = p->value()->dictionaryId();
412   MOZ_ASSERT(dictId < nextDictionaryId);
413   return dictionaryData[dictId].get();
414 }
415 
maybeEventText(TraceLoggerEventPayload * p)416 const char* TraceLoggerThreadState::maybeEventText(TraceLoggerEventPayload* p) {
417   LockGuard<Mutex> guard(lock);
418   if (!p) {
419     return nullptr;
420   }
421 
422   uint32_t dictId = p->dictionaryId();
423   MOZ_ASSERT(dictId < nextDictionaryId);
424   return dictionaryData[dictId].get();
425 }
426 
sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)427 size_t TraceLoggerThreadState::sizeOfExcludingThis(
428     mozilla::MallocSizeOf mallocSizeOf) {
429   LockGuard<Mutex> guard(lock);
430 
431   // Do not count threadLoggers since they are counted by
432   // JSContext::traceLogger.
433 
434   size_t size = 0;
435   size += dictionaryData.sizeOfExcludingThis(mallocSizeOf);
436   size += payloadDictionary.shallowSizeOfExcludingThis(mallocSizeOf);
437   size += textIdPayloads.shallowSizeOfExcludingThis(mallocSizeOf);
438   for (TextIdToPayloadMap::Range r = textIdPayloads.all(); !r.empty();
439        r.popFront()) {
440     r.front().value()->sizeOfIncludingThis(mallocSizeOf);
441   }
442 
443   return size;
444 }
445 
getOrCreateEventPayload(const char * text)446 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
447     const char* text) {
448   LockGuard<Mutex> guard(lock);
449 
450   uint32_t dictId = nextDictionaryId;
451 
452   StringHashToDictionaryMap::AddPtr dictp =
453       payloadDictionary.lookupForAdd(text);
454   if (dictp) {
455     dictId = dictp->value();
456     MOZ_ASSERT(dictId < nextDictionaryId);  // Sanity check.
457   } else {
458     UniqueChars str = DuplicateString(text);
459     if (!str) {
460       return nullptr;
461     }
462     if (!payloadDictionary.add(dictp, str.get(), nextDictionaryId)) {
463       return nullptr;
464     }
465     if (!dictionaryData.append(std::move(str))) {
466       return nullptr;
467     }
468 
469     nextDictionaryId++;
470   }
471 
472   // Look for a free entry, as some textId's may
473   // already be taken from previous profiling sessions.
474   while (textIdPayloads.has(nextTextId)) {
475     nextTextId++;
476   }
477 
478   auto* payload = js_new<TraceLoggerEventPayload>(nextTextId, dictId);
479   if (!payload) {
480     return nullptr;
481   }
482 
483   if (!textIdPayloads.putNew(nextTextId, payload)) {
484     js_delete(payload);
485     return nullptr;
486   }
487 
488   payload->use();
489 
490   nextTextId++;
491 
492   return payload;
493 }
494 
getOrCreateEventPayload(const char * filename,uint32_t lineno,uint32_t colno)495 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
496     const char* filename, uint32_t lineno, uint32_t colno) {
497   if (!filename) {
498     filename = "<unknown>";
499   }
500 
501   TraceLoggerEventPayload* payload = getOrCreateEventPayload(filename);
502   if (!payload) {
503     return nullptr;
504   }
505 
506   payload->setLine(lineno);
507   payload->setColumn(colno);
508 
509   return payload;
510 }
511 
getOrCreateEventPayload(JSScript * script)512 TraceLoggerEventPayload* TraceLoggerThreadState::getOrCreateEventPayload(
513     JSScript* script) {
514   return getOrCreateEventPayload(script->filename(), script->lineno(),
515                                  script->column());
516 }
517 
purgeUnusedPayloads()518 void TraceLoggerThreadState::purgeUnusedPayloads() {
519   // Care needs to be taken to maintain a coherent state in this function,
520   // as payloads can have their use count change at any time from non-zero to
521   // zero (but not the other way around; see TraceLoggerEventPayload::use()).
522   LockGuard<Mutex> guard(lock);
523 
524   // Free all other payloads that have no uses anymore.
525   for (TextIdToPayloadMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) {
526     if (e.front().value()->uses() == 0) {
527       uint32_t dictId = e.front().value()->dictionaryId();
528       dictionaryData.erase(dictionaryData.begin() + dictId);
529       js_delete(e.front().value());
530       e.removeFront();
531     }
532   }
533 }
534 
startEvent(TraceLoggerTextId id)535 void TraceLoggerThread::startEvent(TraceLoggerTextId id) {
536   startEvent(uint32_t(id));
537 }
538 
startEvent(const TraceLoggerEvent & event)539 void TraceLoggerThread::startEvent(const TraceLoggerEvent& event) {
540   if (!event.hasTextId()) {
541     if (!enabled()) {
542       return;
543     }
544     startEvent(TraceLogger_Error);
545     disable(/* force = */ true,
546             "TraceLogger encountered an empty event. "
547             "Potentially due to OOM during creation of "
548             "this event. Disabling TraceLogger.");
549     return;
550   }
551   startEvent(event.textId());
552 }
553 
startEvent(uint32_t id)554 void TraceLoggerThread::startEvent(uint32_t id) {
555   if (!jit::JitOptions.enableTraceLogger) {
556     return;
557   }
558 
559   MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
560   MOZ_ASSERT(traceLoggerState);
561   if (!traceLoggerState->isTextIdEnabled(id)) {
562     return;
563   }
564 
565 #ifdef DEBUG
566   if (enabled_ > 0) {
567     AutoEnterOOMUnsafeRegion oomUnsafe;
568     if (!graphStack_.append(id)) {
569       oomUnsafe.crash("Could not add item to debug stack.");
570     }
571   }
572 #endif
573 
574   if (graph_.get() && traceLoggerState->isGraphFileEnabled()) {
575     // Flush each textId to disk.  textId values up to TraceLogger_Last are
576     // statically defined and each one has an associated constant event string
577     // defined by TLTextIdString().  For any events with textId >=
578     // TraceLogger_Last the payload associated with that textId must first be
579     // found and then maybeEventText() will find the event string form the
580     // dictionary.
581     for (uint32_t otherId = graph_->nextTextId(); otherId <= id; otherId++) {
582       if (TLTextIdIsEnumEvent(id)) {
583         const char* text = TLTextIdString(static_cast<TraceLoggerTextId>(id));
584         graph_->addTextId(otherId, text);
585       } else {
586         TraceLoggerEventPayload* p = traceLoggerState->getPayload(id);
587         if (p) {
588           const char* filename = traceLoggerState->maybeEventText(p);
589           mozilla::Maybe<uint32_t> line = p->line();
590           mozilla::Maybe<uint32_t> column = p->column();
591           graph_->addTextId(otherId, filename, line, column);
592           p->release();
593         }
594       }
595     }
596   }
597 
598   log(id);
599 }
600 
stopEvent(TraceLoggerTextId id)601 void TraceLoggerThread::stopEvent(TraceLoggerTextId id) {
602   stopEvent(uint32_t(id));
603 }
604 
stopEvent(const TraceLoggerEvent & event)605 void TraceLoggerThread::stopEvent(const TraceLoggerEvent& event) {
606   if (!event.hasTextId()) {
607     stopEvent(TraceLogger_Error);
608     return;
609   }
610   stopEvent(event.textId());
611 }
612 
stopEvent(uint32_t id)613 void TraceLoggerThread::stopEvent(uint32_t id) {
614   if (!jit::JitOptions.enableTraceLogger) {
615     return;
616   }
617 
618   MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error);
619   MOZ_ASSERT(traceLoggerState);
620   if (!traceLoggerState->isTextIdEnabled(id)) {
621     return;
622   }
623 
624 #ifdef DEBUG
625   if (!graphStack_.empty()) {
626     uint32_t prev = graphStack_.popCopy();
627     if (id == TraceLogger_Error || prev == TraceLogger_Error) {
628       // When encountering an Error id the stack will most likely not be correct
629       // anymore. Ignore this.
630     } else if (id == TraceLogger_Engine) {
631       MOZ_ASSERT(prev == TraceLogger_IonMonkey ||
632                  prev == TraceLogger_Baseline ||
633                  prev == TraceLogger_Interpreter);
634     } else if (id == TraceLogger_Scripts) {
635       MOZ_ASSERT(TLTextIdIsScriptEvent(prev));
636     } else if (TLTextIdIsScriptEvent(id)) {
637       MOZ_ASSERT(TLTextIdIsScriptEvent(prev));
638       if (prev != id) {
639         // Ignore if the text has been flushed already.
640         MOZ_ASSERT_IF(maybeEventText(prev),
641                       strcmp(maybeEventText(id), maybeEventText(prev)) == 0);
642       }
643     } else {
644       MOZ_ASSERT(id == prev);
645     }
646   }
647 #endif
648 
649   log(TraceLogger_Stop);
650 }
651 
AutoTraceLoggerLockGuard()652 JS::AutoTraceLoggerLockGuard::AutoTraceLoggerLockGuard() {
653   traceLoggerState->lock.lock();
654 }
655 
~AutoTraceLoggerLockGuard()656 JS::AutoTraceLoggerLockGuard::~AutoTraceLoggerLockGuard() {
657   traceLoggerState->lock.unlock();
658 }
659 
NextChunk(JSContext * cx,size_t * dataIndex,char buffer[],size_t bufferSize)660 size_t JS::TraceLoggerDictionaryImpl::NextChunk(JSContext* cx,
661                                                 size_t* dataIndex,
662                                                 char buffer[],
663                                                 size_t bufferSize) {
664   MOZ_ASSERT(dataIndex != nullptr);
665   if (!traceLoggerState || bufferSize == 0 || !buffer ||
666       !jit::JitOptions.enableTraceLogger) {
667     return 0;
668   }
669 
670   size_t bufferIndex = 0;
671 
672   const char* eventString = nullptr;
673   if (TLTextIdIsEnumEvent(*dataIndex)) {
674     eventString = TLTextIdString(static_cast<TraceLoggerTextId>(*dataIndex));
675   } else {
676     uint32_t dictId = *dataIndex - TraceLogger_Last;
677     if (dictId < traceLoggerState->dictionaryData.length()) {
678       eventString = traceLoggerState->dictionaryData[dictId].get();
679       MOZ_ASSERT(eventString);
680     }
681   }
682 
683   if (eventString) {
684     size_t length = strlen(eventString);
685     if (length < bufferSize - 1) {
686       memcpy(buffer, eventString, length);
687       buffer[length] = '\0';
688       bufferIndex = length;
689     } else {
690       memcpy(buffer, eventString, bufferSize);
691       buffer[bufferSize - 1] = '\0';
692       bufferIndex = bufferSize - 1;
693     }
694   }
695 
696   (*dataIndex)++;
697   return bufferIndex;
698 }
699 
NextChunk(JSContext * cx,size_t * dataIndex,uint32_t buffer[],size_t bufferSize)700 size_t JS::TraceLoggerIdImpl::NextChunk(JSContext* cx, size_t* dataIndex,
701                                         uint32_t buffer[], size_t bufferSize) {
702   MOZ_ASSERT(dataIndex != nullptr);
703   if (!cx || !cx->traceLogger) {
704     return 0;
705   }
706 
707   if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
708     return 0;
709   }
710 
711   size_t bufferIndex = 0;
712   ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
713 
714   for (; *dataIndex < events.size(); (*dataIndex)++) {
715     if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
716       continue;
717     }
718 
719     if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
720       TraceLoggerEventPayload* payload =
721           traceLoggerState->getPayload(events[*dataIndex].textId);
722       MOZ_ASSERT(payload);
723       if (!payload) {
724         return 0;
725       }
726       // Write the index of this event into the jsTracerDictionary array
727       // property
728       uint32_t dictId = TraceLogger_Last + payload->dictionaryId();
729       buffer[bufferIndex++] = dictId;
730       payload->release();
731     } else {
732       buffer[bufferIndex++] = events[*dataIndex].textId;
733     }
734 
735     if (bufferIndex == bufferSize) {
736       break;
737     }
738   }
739 
740   return bufferIndex;
741 }
742 
NextChunk(JSContext * cx,size_t * dataIndex,int32_t buffer[],size_t bufferSize)743 size_t JS::TraceLoggerLineNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
744                                             int32_t buffer[],
745                                             size_t bufferSize) {
746   MOZ_ASSERT(dataIndex != nullptr);
747   if (!cx || !cx->traceLogger) {
748     return 0;
749   }
750 
751   if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
752     return 0;
753   }
754 
755   size_t bufferIndex = 0;
756   ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
757 
758   for (; *dataIndex < events.size(); (*dataIndex)++) {
759     if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
760       continue;
761     }
762 
763     if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
764       TraceLoggerEventPayload* payload =
765           traceLoggerState->getPayload(events[*dataIndex].textId);
766       MOZ_ASSERT(payload);
767       if (!payload) {
768         return 0;
769       }
770       mozilla::Maybe<uint32_t> line = payload->line();
771       payload->release();
772       if (line) {
773         buffer[bufferIndex++] = *line;
774       } else {
775         buffer[bufferIndex++] = -1;
776       }
777     } else {
778       buffer[bufferIndex++] = -1;
779     }
780     if (bufferIndex == bufferSize) {
781       break;
782     }
783   }
784 
785   return bufferIndex;
786 }
787 
NextChunk(JSContext * cx,size_t * dataIndex,int32_t buffer[],size_t bufferSize)788 size_t JS::TraceLoggerColNoImpl::NextChunk(JSContext* cx, size_t* dataIndex,
789                                            int32_t buffer[],
790                                            size_t bufferSize) {
791   MOZ_ASSERT(dataIndex != nullptr);
792   if (!cx || !cx->traceLogger) {
793     return 0;
794   }
795 
796   if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
797     return 0;
798   }
799 
800   size_t bufferIndex = 0;
801   ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
802 
803   for (; *dataIndex < events.size(); (*dataIndex)++) {
804     if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
805       continue;
806     }
807 
808     if (TLTextIdIsScriptEvent(events[*dataIndex].textId)) {
809       TraceLoggerEventPayload* payload =
810           traceLoggerState->getPayload(events[*dataIndex].textId);
811       MOZ_ASSERT(payload);
812       if (!payload) {
813         return 0;
814       }
815       mozilla::Maybe<uint32_t> column = payload->column();
816       payload->release();
817       if (column) {
818         buffer[bufferIndex++] = *column;
819       } else {
820         buffer[bufferIndex++] = -1;
821       }
822     } else {
823       buffer[bufferIndex++] = -1;
824     }
825     if (bufferIndex == bufferSize) {
826       break;
827     }
828   }
829 
830   return bufferIndex;
831 }
832 
NextChunk(JSContext * cx,size_t * dataIndex,mozilla::TimeStamp buffer[],size_t bufferSize)833 size_t JS::TraceLoggerTimeStampImpl::NextChunk(JSContext* cx, size_t* dataIndex,
834                                                mozilla::TimeStamp buffer[],
835                                                size_t bufferSize) {
836   MOZ_ASSERT(dataIndex != nullptr);
837   if (!cx || !cx->traceLogger) {
838     return 0;
839   }
840 
841   if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
842     return 0;
843   }
844 
845   size_t bufferIndex = 0;
846   ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
847 
848   for (; *dataIndex < events.size(); (*dataIndex)++) {
849     if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
850       continue;
851     }
852     buffer[bufferIndex++] = events[*dataIndex].time;
853     if (bufferIndex == bufferSize) {
854       break;
855     }
856   }
857 
858   return bufferIndex;
859 }
860 
NextChunk(JSContext * cx,size_t * dataIndex,double buffer[],size_t bufferSize)861 size_t JS::TraceLoggerDurationImpl::NextChunk(JSContext* cx, size_t* dataIndex,
862                                               double buffer[],
863                                               size_t bufferSize) {
864   MOZ_ASSERT(dataIndex != nullptr);
865   if (!cx || !cx->traceLogger) {
866     return 0;
867   }
868 
869   if (bufferSize == 0 || !buffer || !jit::JitOptions.enableTraceLogger) {
870     return 0;
871   }
872 
873   ContinuousSpace<EventEntry>& events = cx->traceLogger->events_;
874   Vector<size_t, 0, js::SystemAllocPolicy> eventStack;
875   using EventDurationMap =
876       HashMap<size_t, double, DefaultHasher<size_t>, SystemAllocPolicy>;
877   EventDurationMap eventMap;
878 
879   size_t bufferIndex = 0;
880   for (; *dataIndex < events.size(); (*dataIndex)++) {
881     if (TLTextIdIsInternalEvent(events[*dataIndex].textId)) {
882       continue;
883     }
884     double duration = 0;
885     if (TLTextIdIsLogEvent(events[*dataIndex].textId)) {
886       // log events are snapshot events with no start & stop
887       duration = -1;
888     } else if (EventDurationMap::Ptr p = eventMap.lookup(*dataIndex)) {
889       // value has already been cached
890       duration = p->value();
891     } else {
892       MOZ_ASSERT(eventStack.empty());
893       if (!eventStack.append(*dataIndex)) {
894         return 0;
895       }
896 
897       // Search through the events array to find the matching stop event in
898       // order to calculate the duration time.  Cache all other durations we
899       // calculate in the meantime.
900       for (size_t j = *dataIndex + 1; j < events.size(); j++) {
901         uint32_t id = events[j].textId;
902         if (id == TraceLogger_Stop) {
903           uint32_t prev = eventStack.popCopy();
904           double delta = (events[j].time - events[prev].time).ToMicroseconds();
905           if (prev == *dataIndex) {
906             MOZ_ASSERT(eventStack.empty());
907             duration = delta;
908             break;
909           }
910 
911           if (!eventMap.putNew(prev, delta)) {
912             return 0;
913           }
914 
915         } else if (TLTextIdIsTreeEvent(id)) {
916           if (!eventStack.append(j)) {
917             return 0;
918           }
919         }
920 
921         // If we reach the end of the list, use the last event as the end
922         // event for all events remaining on the stack.
923         if (j == events.size() - 1) {
924           while (!eventStack.empty()) {
925             uint32_t prev = eventStack.popCopy();
926             double delta =
927                 (events[j].time - events[prev].time).ToMicroseconds();
928             if (prev == *dataIndex) {
929               MOZ_ASSERT(eventStack.empty());
930               duration = delta;
931             } else {
932               if (!eventMap.putNew(prev, delta)) {
933                 return 0;
934               }
935             }
936           }
937         }
938       }
939     }
940 
941     buffer[bufferIndex++] = duration;
942     if (bufferIndex == bufferSize) {
943       break;
944     }
945   }
946 
947   return bufferIndex;
948 }
949 
logTimestamp(TraceLoggerTextId id)950 void TraceLoggerThread::logTimestamp(TraceLoggerTextId id) {
951   logTimestamp(uint32_t(id));
952 }
953 
logTimestamp(uint32_t id)954 void TraceLoggerThread::logTimestamp(uint32_t id) {
955   MOZ_ASSERT(id > TraceLogger_TreeItemEnd && id < TraceLogger_Last);
956   log(id);
957 }
958 
log(uint32_t id)959 void TraceLoggerThread::log(uint32_t id) {
960   if (enabled_ == 0) {
961     return;
962   }
963 
964 #ifdef DEBUG
965   if (id == TraceLogger_Disable) {
966     graphStack_.clear();
967   }
968 #endif
969 
970   MOZ_ASSERT(traceLoggerState);
971 
972   // We request for 3 items to add, since if we don't have enough room
973   // we record the time it took to make more space. To log this information
974   // we need 2 extra free entries.
975   if (!events_.hasSpaceForAdd(3)) {
976     mozilla::TimeStamp start = mozilla::TimeStamp::Now();
977 
978     if (!events_.ensureSpaceBeforeAdd(3)) {
979       if (graph_.get()) {
980         graph_->log(events_, traceLoggerState->startTime);
981       }
982 
983       // The data structures are full, and the graph file is not enabled
984       // so we cannot flush to disk.  Trace logging should stop here.
985       if (!traceLoggerState->isGraphFileEnabled()) {
986         enabled_ = 0;
987         return;
988       }
989 
990       iteration_++;
991       events_.clear();
992 
993       // Periodically remove unused payloads from the global logger state.
994       traceLoggerState->purgeUnusedPayloads();
995     }
996 
997     // Log the time it took to flush the events_ as being from the
998     // Tracelogger.
999     if (graph_.get()) {
1000       MOZ_ASSERT(events_.hasSpaceForAdd(2));
1001       EventEntry& entryStart = events_.pushUninitialized();
1002       entryStart.time = start;
1003       entryStart.textId = TraceLogger_Internal;
1004 
1005       EventEntry& entryStop = events_.pushUninitialized();
1006       entryStop.time = mozilla::TimeStamp::Now();
1007       entryStop.textId = TraceLogger_Stop;
1008     }
1009   }
1010 
1011   mozilla::TimeStamp time = mozilla::TimeStamp::Now();
1012 
1013   EventEntry& entry = events_.pushUninitialized();
1014   entry.time = time;
1015   entry.textId = id;
1016 }
1017 
remapDictionaryEntries(mozilla::Vector<UniqueChars,0,SystemAllocPolicy> * newDictionary,uint32_t * newNextDictionaryId)1018 bool TraceLoggerThreadState::remapDictionaryEntries(
1019     mozilla::Vector<UniqueChars, 0, SystemAllocPolicy>* newDictionary,
1020     uint32_t* newNextDictionaryId) {
1021   MOZ_ASSERT(newNextDictionaryId != nullptr && newDictionary != nullptr);
1022 
1023   typedef HashMap<uint32_t, uint32_t, DefaultHasher<uint32_t>,
1024                   SystemAllocPolicy>
1025       DictionaryMap;
1026   DictionaryMap dictionaryMap;
1027 
1028   // Clear all payloads that are not currently used.  There may be some events
1029   // that still hold a pointer to a payload.  Restarting the profiler may reuse
1030   // the exact same event as a previous session if it's still alive so we need
1031   // to maintain it's existence.
1032   for (TextIdToPayloadMap::Enum e(textIdPayloads); !e.empty(); e.popFront()) {
1033     if (e.front().value()->uses() == 0) {
1034       js_delete(e.front().value());
1035       e.removeFront();
1036     } else {
1037       TraceLoggerEventPayload* payload = e.front().value();
1038       uint32_t dictId = payload->dictionaryId();
1039 
1040       if (dictionaryMap.has(dictId)) {
1041         DictionaryMap::Ptr mapPointer = dictionaryMap.lookup(dictId);
1042         MOZ_ASSERT(mapPointer);
1043         payload->setDictionaryId(mapPointer->value());
1044       } else {
1045         if (!newDictionary->append(std::move(dictionaryData[dictId]))) {
1046           return false;
1047         }
1048         payload->setDictionaryId(*newNextDictionaryId);
1049 
1050         if (!dictionaryMap.putNew(dictId, *newNextDictionaryId)) {
1051           return false;
1052         }
1053 
1054         (*newNextDictionaryId)++;
1055       }
1056     }
1057   }
1058 
1059   return true;
1060 }
1061 
clear()1062 void TraceLoggerThreadState::clear() {
1063   LockGuard<Mutex> guard(lock);
1064 
1065   uint32_t newNextDictionaryId = 0;
1066   mozilla::Vector<UniqueChars, 0, SystemAllocPolicy> newDictionary;
1067   if (remapDictionaryEntries(&newDictionary, &newNextDictionaryId)) {
1068     // Clear and free any data used for the string dictionary.
1069     for (auto range = dictionaryData.all(); !range.empty(); range.popFront()) {
1070       range.front().reset();
1071     }
1072     dictionaryData.clearAndFree();
1073     dictionaryData = std::move(newDictionary);
1074 
1075     payloadDictionary.clearAndCompact();
1076 
1077     nextTextId = TraceLogger_Last;
1078     nextDictionaryId = newNextDictionaryId;
1079   }
1080 
1081   for (TraceLoggerThread* logger : threadLoggers) {
1082     logger->clear();
1083   }
1084 }
1085 
clear()1086 void TraceLoggerThread::clear() {
1087   if (graph_.get()) {
1088     graph_.reset();
1089   }
1090 
1091   graph_ = nullptr;
1092 
1093 #ifdef DEBUG
1094   graphStack_.clear();
1095 #endif
1096 
1097   if (!events_.reset()) {
1098     silentFail("Cannot reset event buffer.");
1099   }
1100 }
1101 
~TraceLoggerThreadState()1102 TraceLoggerThreadState::~TraceLoggerThreadState() {
1103   while (TraceLoggerThread* logger = threadLoggers.popFirst()) {
1104     js_delete(logger);
1105   }
1106 
1107   threadLoggers.clear();
1108 
1109   for (TextIdToPayloadMap::Range r = textIdPayloads.all(); !r.empty();
1110        r.popFront()) {
1111     js_delete(r.front().value());
1112   }
1113 
1114 #ifdef DEBUG
1115   initialized = false;
1116 #endif
1117 }
1118 
init()1119 bool TraceLoggerThreadState::init() {
1120   const char* env = getenv("TLLOG");
1121   if (env) {
1122     if (strstr(env, "help")) {
1123       fflush(nullptr);
1124       printf(
1125           "\n"
1126           "usage: TLLOG=option,option,option,... where options can be:\n"
1127           "\n"
1128           "Collections:\n"
1129           "  Default        Output all default. It includes:\n"
1130           "                 AnnotateScripts, Bailout, Baseline, "
1131           "BaselineCompilation, GC,\n"
1132           "                 GCAllocation, GCSweeping, Interpreter, "
1133           "IonAnalysis, IonCompilation,\n"
1134           "                 IonLinking, IonMonkey, MinorGC, Frontend, "
1135           "ParsingFull,\n"
1136           "                 ParsingSyntax, BytecodeEmission, IrregexpCompile, "
1137           "IrregexpExecute,\n"
1138           "                 Scripts, Engine, WasmCompilation\n"
1139           "\n"
1140           "  IonCompiler    Output all information about compilation. It "
1141           "includes:\n"
1142           "                 IonCompilation, IonLinking, PruneUnusedBranches, "
1143           "FoldTests,\n"
1144           "                 SplitCriticalEdges, RenumberBlocks, "
1145           "ScalarReplacement,\n"
1146           "                 DominatorTree, PhiAnalysis, MakeLoopsContiguous, "
1147           "ApplyTypes,\n"
1148           "                 EagerSimdUnbox, AliasAnalysis, GVN, LICM, "
1149           "RangeAnalysis,\n"
1150           "                 LoopUnrolling, FoldLinearArithConstants, "
1151           "EffectiveAddressAnalysis,\n"
1152           "                 AlignmentMaskAnalysis, EliminateDeadCode, "
1153           "ReorderInstructions,\n"
1154           "                 EdgeCaseAnalysis, EliminateRedundantChecks,\n"
1155           "                 AddKeepAliveInstructions, GenerateLIR, "
1156           "RegisterAllocation,\n"
1157           "                 GenerateCode, Scripts\n"
1158           "\n"
1159           "  VMSpecific     Output the specific name of the VM call\n"
1160           "\n"
1161           "  Frontend       Output all information about frontend compilation. "
1162           "It includes:\n"
1163           "                 Frontend, ParsingFull, ParsingSyntax, Tokenizing,\n"
1164           "                 BytecodeEmission, BytecodeFoldConstants, "
1165           "BytecodeNameFunctions\n"
1166           "Specific log items:\n");
1167       for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1168         TraceLoggerTextId id = TraceLoggerTextId(i);
1169         if (!TLTextIdIsTogglable(id)) {
1170           continue;
1171         }
1172         printf("  %s\n", TLTextIdString(id));
1173       }
1174       printf("\n");
1175       exit(0);
1176       /*NOTREACHED*/
1177     }
1178 
1179     for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1180       TraceLoggerTextId id = TraceLoggerTextId(i);
1181       if (TLTextIdIsTogglable(id)) {
1182         enabledTextIds[i] = ContainsFlag(env, TLTextIdString(id));
1183       } else {
1184         enabledTextIds[i] = true;
1185       }
1186     }
1187 
1188     if (ContainsFlag(env, "Default")) {
1189       enableDefaultLogging();
1190     }
1191 
1192     if (ContainsFlag(env, "IonCompiler")) {
1193       enableIonLogging();
1194     }
1195 
1196     if (ContainsFlag(env, "Frontend")) {
1197       enableFrontendLogging();
1198     }
1199 
1200 #ifdef DEBUG
1201     enabledTextIds[TraceLogger_Error] = true;
1202 #endif
1203 
1204   } else {
1205     // Most of the textId's will be enabled through JS::StartTraceLogger when
1206     // the gecko profiler is started.
1207     disableTextIdsForProfiler();
1208   }
1209 
1210   enabledTextIds[TraceLogger_Interpreter] = enabledTextIds[TraceLogger_Engine];
1211   enabledTextIds[TraceLogger_Baseline] = enabledTextIds[TraceLogger_Engine];
1212   enabledTextIds[TraceLogger_IonMonkey] = enabledTextIds[TraceLogger_Engine];
1213 
1214   enabledTextIds[TraceLogger_Error] = true;
1215 
1216   const char* options = getenv("TLOPTIONS");
1217   if (options) {
1218     if (strstr(options, "help")) {
1219       fflush(nullptr);
1220       printf(
1221           "\n"
1222           "usage: TLOPTIONS=option,option,option,... where options can be:\n"
1223           "\n"
1224           "  EnableMainThread        Start logging main threads immediately.\n"
1225           "  EnableOffThread         Start logging helper threads "
1226           "immediately.\n"
1227           "  EnableGraph             Enable the tracelogging graph.\n"
1228           "  EnableGraphFile         Enable flushing tracelogger data to a "
1229           "file.\n"
1230           "  Errors                  Report errors during tracing to "
1231           "stderr.\n");
1232       printf("\n");
1233       exit(0);
1234       /*NOTREACHED*/
1235     }
1236 
1237     if (strstr(options, "EnableMainThread")) {
1238       mainThreadEnabled = true;
1239     }
1240     if (strstr(options, "EnableOffThread")) {
1241       helperThreadEnabled = true;
1242     }
1243     if (strstr(options, "EnableGraph")) {
1244       graphEnabled = true;
1245     }
1246     if (strstr(options, "EnableGraphFile")) {
1247       graphFileEnabled = true;
1248       jit::JitOptions.enableTraceLogger = true;
1249     }
1250     if (strstr(options, "Errors")) {
1251       spewErrors = true;
1252     }
1253   } else {
1254     mainThreadEnabled = true;
1255     helperThreadEnabled = true;
1256     graphEnabled = false;
1257     graphFileEnabled = false;
1258     spewErrors = false;
1259   }
1260 
1261   startTime = mozilla::TimeStamp::Now();
1262 
1263 #ifdef DEBUG
1264   initialized = true;
1265 #endif
1266 
1267   return true;
1268 }
1269 
enableTextId(JSContext * cx,uint32_t textId)1270 void TraceLoggerThreadState::enableTextId(JSContext* cx, uint32_t textId) {
1271   MOZ_ASSERT(TLTextIdIsTogglable(textId));
1272 
1273   if (enabledTextIds[textId]) {
1274     return;
1275   }
1276 
1277   ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
1278 
1279   enabledTextIds[textId] = true;
1280   if (textId == TraceLogger_Engine) {
1281     enabledTextIds[TraceLogger_IonMonkey] = true;
1282     enabledTextIds[TraceLogger_Baseline] = true;
1283     enabledTextIds[TraceLogger_Interpreter] = true;
1284   }
1285 
1286   if (textId == TraceLogger_Scripts) {
1287     jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), true);
1288   }
1289   if (textId == TraceLogger_Engine) {
1290     jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), true);
1291   }
1292 }
disableTextId(JSContext * cx,uint32_t textId)1293 void TraceLoggerThreadState::disableTextId(JSContext* cx, uint32_t textId) {
1294   MOZ_ASSERT(TLTextIdIsTogglable(textId));
1295 
1296   if (!enabledTextIds[textId]) {
1297     return;
1298   }
1299 
1300   ReleaseAllJITCode(cx->runtime()->defaultFreeOp());
1301 
1302   enabledTextIds[textId] = false;
1303   if (textId == TraceLogger_Engine) {
1304     enabledTextIds[TraceLogger_IonMonkey] = false;
1305     enabledTextIds[TraceLogger_Baseline] = false;
1306     enabledTextIds[TraceLogger_Interpreter] = false;
1307   }
1308 
1309   if (textId == TraceLogger_Scripts) {
1310     jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), false);
1311   }
1312   if (textId == TraceLogger_Engine) {
1313     jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), false);
1314   }
1315 }
1316 
TraceLoggerForCurrentThread(JSContext * maybecx)1317 TraceLoggerThread* js::TraceLoggerForCurrentThread(JSContext* maybecx) {
1318   if (!traceLoggerState) {
1319     return nullptr;
1320   }
1321   return traceLoggerState->forCurrentThread(maybecx);
1322 }
1323 
forCurrentThread(JSContext * maybecx)1324 TraceLoggerThread* TraceLoggerThreadState::forCurrentThread(
1325     JSContext* maybecx) {
1326   if (!jit::JitOptions.enableTraceLogger) {
1327     return nullptr;
1328   }
1329 
1330   MOZ_ASSERT(initialized);
1331   MOZ_ASSERT_IF(maybecx, maybecx == TlsContext.get());
1332 
1333   JSContext* cx = maybecx ? maybecx : TlsContext.get();
1334   if (!cx) {
1335     return nullptr;
1336   }
1337 
1338   if (!cx->traceLogger) {
1339     LockGuard<Mutex> guard(lock);
1340 
1341     TraceLoggerThread* logger = js_new<TraceLoggerThread>(cx);
1342     if (!logger) {
1343       return nullptr;
1344     }
1345 
1346     if (!logger->init()) {
1347       return nullptr;
1348     }
1349 
1350     threadLoggers.insertFront(logger);
1351     cx->traceLogger = logger;
1352 
1353     if (graphEnabled) {
1354       logger->initGraph();
1355     }
1356 
1357     if (cx->isHelperThreadContext() ? helperThreadEnabled : mainThreadEnabled) {
1358       logger->enable();
1359     }
1360   }
1361 
1362   return cx->traceLogger;
1363 }
1364 
destroyLogger(TraceLoggerThread * logger)1365 void TraceLoggerThreadState::destroyLogger(TraceLoggerThread* logger) {
1366   MOZ_ASSERT(initialized);
1367   MOZ_ASSERT(logger);
1368   LockGuard<Mutex> guard(lock);
1369 
1370   logger->remove();
1371   js_delete(logger);
1372 }
1373 
TraceLogTextIdEnabled(uint32_t textId)1374 bool js::TraceLogTextIdEnabled(uint32_t textId) {
1375   if (!traceLoggerState) {
1376     return false;
1377   }
1378   return traceLoggerState->isTextIdEnabled(textId);
1379 }
1380 
TraceLogEnableTextId(JSContext * cx,uint32_t textId)1381 void js::TraceLogEnableTextId(JSContext* cx, uint32_t textId) {
1382   if (!traceLoggerState) {
1383     return;
1384   }
1385   traceLoggerState->enableTextId(cx, textId);
1386 }
TraceLogDisableTextId(JSContext * cx,uint32_t textId)1387 void js::TraceLogDisableTextId(JSContext* cx, uint32_t textId) {
1388   if (!traceLoggerState) {
1389     return;
1390   }
1391   traceLoggerState->disableTextId(cx, textId);
1392 }
1393 
TraceLoggerEvent(TraceLoggerTextId type,JSScript * script)1394 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, JSScript* script)
1395     : TraceLoggerEvent(type, script->filename(), script->lineno(),
1396                        script->column()) {}
1397 
TraceLoggerEvent(TraceLoggerTextId type,const char * filename,uint32_t line,uint32_t column)1398 TraceLoggerEvent::TraceLoggerEvent(TraceLoggerTextId type, const char* filename,
1399                                    uint32_t line, uint32_t column)
1400     : payload_() {
1401   MOZ_ASSERT(
1402       type == TraceLogger_Scripts || type == TraceLogger_AnnotateScripts ||
1403       type == TraceLogger_InlinedScripts || type == TraceLogger_Frontend);
1404 
1405   if (!traceLoggerState || !jit::JitOptions.enableTraceLogger) {
1406     return;
1407   }
1408 
1409   // Only log scripts when enabled, otherwise use the more generic type
1410   // (which will get filtered out).
1411   if (!traceLoggerState->isTextIdEnabled(type)) {
1412     payload_.setTextId(type);
1413     return;
1414   }
1415 
1416   payload_.setEventPayload(
1417       traceLoggerState->getOrCreateEventPayload(filename, line, column));
1418 }
1419 
TraceLoggerEvent(const char * text)1420 TraceLoggerEvent::TraceLoggerEvent(const char* text) : payload_() {
1421   if (jit::JitOptions.enableTraceLogger && traceLoggerState) {
1422     payload_.setEventPayload(traceLoggerState->getOrCreateEventPayload(text));
1423   }
1424 }
1425 
~TraceLoggerEvent()1426 TraceLoggerEvent::~TraceLoggerEvent() {
1427   if (hasExtPayload()) {
1428     extPayload()->release();
1429   }
1430 }
1431 
textId() const1432 uint32_t TraceLoggerEvent::textId() const {
1433   MOZ_ASSERT(hasTextId());
1434   if (hasExtPayload()) {
1435     return extPayload()->textId();
1436   }
1437   return payload_.textId();
1438 }
1439 
operator =(const TraceLoggerEvent & other)1440 TraceLoggerEvent& TraceLoggerEvent::operator=(const TraceLoggerEvent& other) {
1441   if (other.hasExtPayload()) {
1442     other.extPayload()->use();
1443   }
1444   if (hasExtPayload()) {
1445     extPayload()->release();
1446   }
1447 
1448   payload_ = other.payload_;
1449 
1450   return *this;
1451 }
1452 
TraceLoggerEvent(const TraceLoggerEvent & other)1453 TraceLoggerEvent::TraceLoggerEvent(const TraceLoggerEvent& other)
1454     : payload_(other.payload_) {
1455   if (hasExtPayload()) {
1456     extPayload()->use();
1457   }
1458 }
1459 
InitTraceLogger()1460 JS_PUBLIC_API bool JS::InitTraceLogger() {
1461   MOZ_RELEASE_ASSERT(!traceLoggerState);
1462 
1463   if (!getTraceLoggerSupported()) {
1464     return true;
1465   }
1466 
1467   traceLoggerState = js_new<TraceLoggerThreadState>();
1468   if (!traceLoggerState) {
1469     return false;
1470   }
1471 
1472   if (!traceLoggerState->init()) {
1473     DestroyTraceLoggerThreadState();
1474     return false;
1475   }
1476 
1477   return true;
1478 }
1479 
TraceLoggerSupported()1480 JS_PUBLIC_API bool JS::TraceLoggerSupported() { return traceLoggerState; }
1481 
1482 // Perform a process wide synchronous spew of every thread that tracelogger has
1483 // captured.
spewTraceLoggerStats()1484 void TraceLoggerThreadState::spewTraceLoggerStats() {
1485   for (TraceLoggerThread* logger : threadLoggers) {
1486     logger->spewTraceLoggerStats();
1487   }
1488 }
1489 
1490 // Usage here is JS_TRACELOGGER_SPEW=<event1>,<event2>,etc for custom spewing.
1491 // If the environment variable is not found, we use a default set of events.
getSpewIds(EventVector & spewIds)1492 static bool getSpewIds(EventVector& spewIds) {
1493   const char* env = getenv("JS_TRACELOGGER_SPEW");
1494   if (env) {
1495     for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1496       TraceLoggerTextId id = TraceLoggerTextId(i);
1497       if (ContainsFlag(env, TLTextIdString(id))) {
1498         if (!spewIds.append(id)) {
1499           return false;
1500         }
1501       }
1502     }
1503   } else {
1504     const uint32_t defaultSpewEvents[] = {
1505         TraceLogger_ParsingFull, TraceLogger_Interpreter,
1506         TraceLogger_Baseline,    TraceLogger_BaselineCompilation,
1507         TraceLogger_IonMonkey,   TraceLogger_IonCompilation,
1508         TraceLogger_Bailout};
1509 
1510     for (uint32_t id : defaultSpewEvents) {
1511       if (!spewIds.append(id)) {
1512         return false;
1513       }
1514     }
1515   }
1516 
1517   return true;
1518 }
1519 
spewHeaderRow(UniqueChars & threadName,EventVector & spewIds)1520 static void spewHeaderRow(UniqueChars& threadName, EventVector& spewIds) {
1521   if (threadName) {
1522     JitSpew(jit::JitSpew_ScriptStats, "Thread: %s (pid=%d)", threadName.get(),
1523             getpid());
1524   } else {
1525     JitSpew(jit::JitSpew_ScriptStats, "Unknown Thread (pid=%d)", getpid());
1526   }
1527 
1528   UniqueChars header = JS_smprintf("%10s ", "totalTime");
1529   for (uint32_t i : spewIds) {
1530     TraceLoggerTextId id = TraceLoggerTextId(i);
1531     if (TLTextIdIsLogEvent(id)) {
1532       header =
1533           JS_sprintf_append(std::move(header), "%12s ", TLTextIdString(id));
1534     } else {
1535       header =
1536           JS_sprintf_append(std::move(header), "%25s ", TLTextIdString(id));
1537     }
1538   }
1539 
1540   JitSpew(jit::JitSpew_ScriptStats, "%s Script", header.get());
1541 }
1542 
spewTraceLoggerStats()1543 void TraceLoggerThread::spewTraceLoggerStats() {
1544   if (!jit::JitOptions.enableTraceLogger) {
1545     return;
1546   }
1547 
1548   ScriptMap map;
1549   if (!collectTraceLoggerStats(map)) {
1550     return;
1551   }
1552   if (map.empty()) {
1553     return;
1554   }
1555 
1556   SortedStatsVector sorted_map;
1557   if (!sortTraceLoggerStats(map, sorted_map)) {
1558     return;
1559   }
1560   map.clearAndCompact();
1561 
1562   EventVector spewIds;
1563   if (!getSpewIds(spewIds)) {
1564     return;
1565   }
1566 
1567   // Dynamically generate the header row in JitSpew.
1568   spewHeaderRow(threadName_, spewIds);
1569 
1570   for (UniquePtr<ScriptStats>& datap : sorted_map) {
1571     auto& tlevents = datap->events_;
1572     uint32_t selfTime = datap->selfTime;
1573 
1574     if (selfTime == 0) {
1575       continue;
1576     }
1577 
1578     UniqueChars row = JS_smprintf("%10u ", selfTime);
1579     for (uint32_t i : spewIds) {
1580       TraceLoggerTextId id = TraceLoggerTextId(i);
1581       uint32_t time = tlevents[id].time;
1582       uint32_t freq = tlevents[id].count;
1583       uint32_t percent = (time * 100) / selfTime;
1584       if (TLTextIdIsLogEvent(id)) {
1585         row = JS_sprintf_append(std::move(row), "%12u ", freq);
1586       } else {
1587         row = JS_sprintf_append(std::move(row), "%8u (%3u%%,f=%-7u) ", time,
1588                                 percent, freq);
1589       }
1590     }
1591     JitSpew(jit::JitSpew_ScriptStats, "%s %s", row.get(), datap->scriptName);
1592 
1593     // If structured spewer is enabled, we might as well spew everything.
1594     AutoStructuredSpewer spew(cx_, SpewChannel::ScriptStats, nullptr);
1595     if (spew) {
1596       spew->property("script", datap->scriptName);
1597       spew->property("totalTime", selfTime);
1598       spew->beginListProperty("events");
1599       for (uint32_t i = 1; i < TraceLogger_Last; i++) {
1600         TraceLoggerTextId id = TraceLoggerTextId(i);
1601         if (TLTextIdIsInternalEvent(id) || tlevents[id].count == 0) {
1602           continue;
1603         }
1604 
1605         spew->beginObject();
1606         spew->property("id", TLTextIdString(id));
1607         if (TLTextIdIsTreeEvent(id)) {
1608           spew->property("time", tlevents[id].time);
1609           spew->property("frequency", tlevents[id].count);
1610         } else if (TLTextIdIsLogEvent(id)) {
1611           spew->property("frequency", tlevents[id].count);
1612         }
1613         spew->endObject();
1614       }
1615       spew->endList();
1616     }
1617   }
1618 }
1619 
updateScriptMap(ScriptMap & map,char * key,uint32_t eventId,uint32_t value)1620 static bool updateScriptMap(ScriptMap& map, char* key, uint32_t eventId,
1621                             uint32_t value) {
1622   if (!key) {
1623     return false;
1624   }
1625 
1626   if (!map.has(key)) {
1627     UniquePtr<ScriptStats> datap;
1628     datap.reset(js_new<ScriptStats>(key));
1629     if (!map.putNew(key, std::move(datap))) {
1630       return false;
1631     }
1632   }
1633   ScriptMap::Ptr p = map.lookup(key);
1634   p->value()->events_[eventId].time += value;
1635   p->value()->events_[eventId].count++;
1636 
1637   if (TLTextIdIsTreeEvent(eventId)) {
1638     p->value()->selfTime += value;
1639   }
1640   return true;
1641 }
1642 
getFullScriptName(uint32_t textId)1643 UniqueChars TraceLoggerThreadState::getFullScriptName(uint32_t textId) {
1644   TraceLoggerEventPayload* payload = getPayload(textId);
1645   MOZ_ASSERT(payload);
1646   if (!payload) {
1647     return nullptr;
1648   }
1649   char* filename = dictionaryData[payload->dictionaryId()].get();
1650   uint32_t lineno = payload->line() ? *(payload->line()) : 0;
1651   uint32_t colno = payload->column() ? *(payload->column()) : 0;
1652   UniqueChars scriptName = JS_smprintf("%s:%u:%u", filename, lineno, colno);
1653   payload->release();
1654   return scriptName;
1655 }
1656 
sortBySelfTime(const UniquePtr<ScriptStats> & lhs,const UniquePtr<ScriptStats> & rhs)1657 static bool sortBySelfTime(const UniquePtr<ScriptStats>& lhs,
1658                            const UniquePtr<ScriptStats>& rhs) {
1659   return lhs.get()->selfTime > rhs.get()->selfTime;
1660 }
1661 
sortTraceLoggerStats(ScriptMap & map,SortedStatsVector & sorted_map)1662 bool TraceLoggerThread::sortTraceLoggerStats(ScriptMap& map,
1663                                              SortedStatsVector& sorted_map) {
1664   for (auto range = map.all(); !range.empty(); range.popFront()) {
1665     if (!sorted_map.append(std::move(range.front().value()))) {
1666       return false;
1667     }
1668   }
1669 
1670   std::sort(sorted_map.begin(), sorted_map.end(), sortBySelfTime);
1671 
1672   return true;
1673 }
1674 
1675 // Traverse each event and calculate the self-time, along with the script that
1676 // each event belongs to. We do this quickly by maintaining two stacks:
1677 //  (i) eventStack - Each new event encountered is pushed onto the stack. Events
1678 //                   are popped off whenever a TraceLogger_Stop is encountered
1679 //                   and sent to updateScriptMap.
1680 // (ii) funcStack - Each new script encountered is pushed onto this stack.
1681 //                  Elements are popped off whenever a TraceLogger_Stop is
1682 //                  encountered that matches a script event on the top of
1683 //                  eventStack.
collectTraceLoggerStats(ScriptMap & map)1684 bool TraceLoggerThread::collectTraceLoggerStats(ScriptMap& map) {
1685   struct eventInfo {
1686     uint32_t textId;
1687     uint32_t time;
1688     mozilla::TimeStamp start;
1689 
1690     explicit eventInfo(uint32_t textId_) : textId(textId_), time(0) {}
1691   };
1692 
1693   Vector<eventInfo*, 0, js::SystemAllocPolicy> eventStack;
1694   Vector<uint32_t, 0, js::SystemAllocPolicy> funcStack;
1695 
1696   mozilla::TimeStamp startTime, stopTime;
1697   uint32_t size = events_.size();
1698   for (size_t i = 0; i < size; i++) {
1699     uint32_t textId = events_[i].textId;
1700 
1701     // Record any log events that have no durations such as Bailouts with a
1702     // value of 1.  Make sure the funcStack actually has something in it or
1703     // else the Bailout event will not be associated with any script.  This
1704     // can commonly occur when profiling & tracing starts since we may have
1705     // already past the point where the script event is created.
1706     if (TLTextIdIsLogEvent(textId) && !funcStack.empty()) {
1707       UniqueChars script =
1708           traceLoggerState->getFullScriptName(funcStack.back());
1709       if (!updateScriptMap(map, script.release(), textId, 1)) {
1710         return false;
1711       }
1712     }
1713 
1714     // Hit a new tree event or a stop event, so add (new event timestamp - old
1715     // event timestamp) to the old event's self-time.
1716     if (TLTextIdIsTreeEvent(textId)) {
1717       if (!eventStack.empty()) {
1718         stopTime = events_[i].time;
1719 
1720         uint32_t deltaTime =
1721             static_cast<uint32_t>((stopTime - startTime).ToMicroseconds());
1722         eventStack.back()->time += deltaTime;
1723 
1724         if (TLTextIdIsEnumEvent(eventStack.back()->textId)) {
1725           if (!funcStack.empty() && !eventStack.empty()) {
1726             UniqueChars script =
1727                 traceLoggerState->getFullScriptName(funcStack.back());
1728             if (!updateScriptMap(map, script.release(),
1729                                  eventStack.back()->textId, deltaTime)) {
1730               return false;
1731             }
1732           }
1733         }
1734       }
1735 
1736       if (TLTextIdIsScriptEvent(textId)) {
1737         if (!funcStack.append(textId)) {
1738           return false;
1739         }
1740       }
1741 
1742       eventInfo* entry = js_new<eventInfo>(textId);
1743       entry->start = events_[i].time;
1744       if (!eventStack.append(entry)) {
1745         return false;
1746       }
1747 
1748       startTime = events_[i].time;
1749 
1750     } else if (textId == TraceLogger_Stop) {
1751       if (!eventStack.empty()) {
1752         stopTime = events_[i].time;
1753 
1754         uint32_t deltaTime =
1755             static_cast<uint32_t>((stopTime - startTime).ToMicroseconds());
1756         eventInfo* entry = eventStack.popCopy();
1757 
1758         uint32_t topId = entry->textId;
1759         entry->time += deltaTime;
1760 
1761         if (TLTextIdIsEnumEvent(topId)) {
1762           // funcStack will usually be empty near the beginning of a profiling
1763           // session since we may have skipped the point where the script event
1764           // is created.  If that's the case, then skip this event since we
1765           // cannot associate it with any script.
1766           if (!funcStack.empty()) {
1767             UniqueChars script =
1768                 traceLoggerState->getFullScriptName(funcStack.back());
1769             if (!updateScriptMap(map, script.release(), topId, deltaTime)) {
1770               return false;
1771             }
1772           }
1773         }
1774         js_delete(entry);
1775 
1776         if (TLTextIdIsScriptEvent(topId) && !funcStack.empty()) {
1777           funcStack.popBack();
1778         }
1779 
1780         startTime = events_[i].time;
1781       }
1782     }
1783   }
1784 
1785   return true;
1786 }
1787 
ResetTraceLogger(void)1788 JS_PUBLIC_API void JS::ResetTraceLogger(void) { js::ResetTraceLogger(); }
1789 
SpewTraceLoggerThread(JSContext * cx)1790 JS_PUBLIC_API void JS::SpewTraceLoggerThread(JSContext* cx) {
1791   if (!traceLoggerState) {
1792     return;
1793   }
1794 
1795   if (cx && cx->traceLogger) {
1796     cx->traceLogger->spewTraceLoggerStats();
1797   }
1798 }
1799 
SpewTraceLoggerForCurrentProcess()1800 JS_PUBLIC_API void JS::SpewTraceLoggerForCurrentProcess() {
1801   if (!traceLoggerState) {
1802     return;
1803   }
1804   traceLoggerState->spewTraceLoggerStats();
1805 }
1806 
StartTraceLogger(JSContext * cx)1807 JS_PUBLIC_API void JS::StartTraceLogger(JSContext* cx) {
1808   if (!traceLoggerState) {
1809     return;
1810   }
1811 
1812   if (!jit::JitOptions.enableTraceLogger) {
1813     LockGuard<Mutex> guard(traceLoggerState->lock);
1814     traceLoggerState->enableTextIdsForProfiler();
1815     jit::JitOptions.enableTraceLogger = true;
1816     traceLoggerState->startTime = mozilla::TimeStamp::Now();
1817   }
1818 
1819   TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
1820   if (logger) {
1821     logger->enable();
1822   }
1823 }
1824 
StopTraceLogger(JSContext * cx)1825 JS_PUBLIC_API void JS::StopTraceLogger(JSContext* cx) {
1826   if (!traceLoggerState) {
1827     return;
1828   }
1829 
1830   if (jit::JitOptions.enableTraceLogger) {
1831     LockGuard<Mutex> guard(traceLoggerState->lock);
1832     traceLoggerState->disableTextIdsForProfiler();
1833     jit::JitOptions.enableTraceLogger = false;
1834   }
1835 
1836   TraceLoggerThread* logger = traceLoggerState->forCurrentThread(cx);
1837   if (logger) {
1838     logger->disable();
1839   }
1840 }
1841