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 "jit/PerfSpewer.h"
8 
9 #include "mozilla/IntegerPrintfMacros.h"
10 #include "mozilla/Printf.h"
11 
12 #ifdef XP_UNIX
13 #  include <unistd.h>
14 #endif
15 
16 #ifdef JS_ION_PERF
17 #  include "jit/JitSpewer.h"
18 #  include "jit/LIR.h"
19 #  include "jit/MIR.h"
20 #  include "jit/MIRGraph.h"
21 #endif
22 
23 #include "vm/MutexIDs.h"
24 
25 // perf expects its data to be in a file /tmp/perf-PID.map, but for Android
26 // and B2G the map files are written to /data/local/tmp/perf-PID.map
27 //
28 // Except that Android 4.3 no longer allows the browser to write to
29 // /data/local/tmp/ so also try /sdcard/.
30 
31 #ifndef PERF_SPEW_DIR
32 #  if defined(__ANDROID__)
33 #    define PERF_SPEW_DIR "/data/local/tmp/"
34 #    define PERF_SPEW_DIR_2 "/sdcard/"
35 #  else
36 #    define PERF_SPEW_DIR "/tmp/"
37 #  endif
38 #endif
39 
40 using namespace js;
41 using namespace js::jit;
42 
43 #define PERF_MODE_NONE 1
44 #define PERF_MODE_FUNC 2
45 #define PERF_MODE_BLOCK 3
46 
47 #ifdef JS_ION_PERF
48 
49 static uint32_t PerfMode = 0;
50 
51 static bool PerfChecked = false;
52 
53 static FILE* PerfFilePtr = nullptr;
54 
55 static js::Mutex* PerfMutex;
56 
openPerfMap(const char * dir)57 static bool openPerfMap(const char* dir) {
58   const ssize_t bufferSize = 256;
59   char filenameBuffer[bufferSize];
60 
61   if (snprintf(filenameBuffer, bufferSize, "%sperf-%d.map", dir, getpid()) >=
62       bufferSize) {
63     return false;
64   }
65 
66   MOZ_ASSERT(!PerfFilePtr);
67   PerfFilePtr = fopen(filenameBuffer, "a");
68 
69   if (!PerfFilePtr) {
70     return false;
71   }
72 
73   return true;
74 }
75 
CheckPerf()76 void js::jit::CheckPerf() {
77   if (!PerfChecked) {
78     const char* env = getenv("IONPERF");
79     if (env == nullptr) {
80       PerfMode = PERF_MODE_NONE;
81       fprintf(stderr,
82               "Warning: JIT perf reporting requires IONPERF set to \"block\" "
83               "or \"func\". ");
84       fprintf(stderr, "Perf mapping will be deactivated.\n");
85     } else if (!strcmp(env, "none")) {
86       PerfMode = PERF_MODE_NONE;
87     } else if (!strcmp(env, "block")) {
88       PerfMode = PERF_MODE_BLOCK;
89     } else if (!strcmp(env, "func")) {
90       PerfMode = PERF_MODE_FUNC;
91     } else {
92       fprintf(stderr, "Use IONPERF=func to record at function granularity\n");
93       fprintf(stderr,
94               "Use IONPERF=block to record at basic block granularity\n");
95       fprintf(stderr, "\n");
96       fprintf(stderr, "Be advised that using IONPERF will cause all scripts\n");
97       fprintf(stderr, "to be leaked.\n");
98       exit(0);
99     }
100 
101     if (PerfMode != PERF_MODE_NONE) {
102       PerfMutex = js_new<js::Mutex>(mutexid::PerfSpewer);
103       if (!PerfMutex) {
104         MOZ_CRASH("failed to allocate PerfMutex");
105       }
106 
107       if (openPerfMap(PERF_SPEW_DIR)) {
108         PerfChecked = true;
109         return;
110       }
111 
112 #  if defined(__ANDROID__)
113       if (openPerfMap(PERF_SPEW_DIR_2)) {
114         PerfChecked = true;
115         return;
116       }
117 #  endif
118       fprintf(stderr, "Failed to open perf map file.  Disabling IONPERF.\n");
119       PerfMode = PERF_MODE_NONE;
120     }
121     PerfChecked = true;
122   }
123 }
124 
PerfBlockEnabled()125 bool js::jit::PerfBlockEnabled() {
126   MOZ_ASSERT(PerfMode);
127   return PerfMode == PERF_MODE_BLOCK;
128 }
129 
PerfFuncEnabled()130 bool js::jit::PerfFuncEnabled() {
131   MOZ_ASSERT(PerfMode);
132   return PerfMode == PERF_MODE_FUNC;
133 }
134 
135 namespace {
136 struct MOZ_RAII AutoLockPerfMap {
AutoLockPerfMap__anon001ab7830111::AutoLockPerfMap137   AutoLockPerfMap() {
138     if (!PerfEnabled()) {
139       return;
140     }
141     PerfMutex->lock();
142     MOZ_ASSERT(PerfFilePtr);
143   }
~AutoLockPerfMap__anon001ab7830111::AutoLockPerfMap144   ~AutoLockPerfMap() {
145     MOZ_ASSERT(PerfFilePtr);
146     fflush(PerfFilePtr);
147     PerfMutex->unlock();
148   }
149 };
150 }  // namespace
151 
152 uint32_t PerfSpewer::nextFunctionIndex = 0;
153 
startBasicBlock(MBasicBlock * blk,MacroAssembler & masm)154 bool PerfSpewer::startBasicBlock(MBasicBlock* blk, MacroAssembler& masm) {
155   if (!PerfBlockEnabled()) {
156     return true;
157   }
158 
159   const char* filename = blk->info().script()->filename();
160   unsigned lineNumber, columnNumber;
161   if (blk->pc()) {
162     lineNumber = PCToLineNumber(blk->info().script(), blk->pc(), &columnNumber);
163   } else {
164     lineNumber = 0;
165     columnNumber = 0;
166   }
167   Record r(filename, lineNumber, columnNumber, blk->id());
168   masm.bind(&r.start);
169   return basicBlocks_.append(r);
170 }
171 
endBasicBlock(MacroAssembler & masm)172 void PerfSpewer::endBasicBlock(MacroAssembler& masm) {
173   if (!PerfBlockEnabled()) {
174     return;
175   }
176   masm.bind(&basicBlocks_.back().end);
177 }
178 
noteEndInlineCode(MacroAssembler & masm)179 void PerfSpewer::noteEndInlineCode(MacroAssembler& masm) {
180   if (!PerfBlockEnabled()) {
181     return;
182   }
183   masm.bind(&endInlineCode);
184 }
185 
WriteEntry(const AutoLockPerfMap &,uintptr_t address,size_t size,const char * fmt,...)186 void PerfSpewer::WriteEntry(const AutoLockPerfMap&, uintptr_t address,
187                             size_t size, const char* fmt, ...) {
188   va_list ap;
189   va_start(ap, fmt);
190 
191   auto result = mozilla::Vsmprintf<js::SystemAllocPolicy>(fmt, ap);
192   va_end(ap);
193 
194   fprintf(PerfFilePtr, "%" PRIxPTR " %zx %s\n", address, size, result.get());
195 }
196 
writeProfile(JSScript * script,JitCode * code,MacroAssembler & masm)197 void PerfSpewer::writeProfile(JSScript* script, JitCode* code,
198                               MacroAssembler& masm) {
199   AutoLockPerfMap lock;
200 
201   if (PerfFuncEnabled()) {
202     uint32_t thisFunctionIndex = nextFunctionIndex++;
203     size_t size = code->instructionsSize();
204     if (size > 0) {
205       WriteEntry(lock, reinterpret_cast<uintptr_t>(code->raw()), size,
206                  "%s:%u: Func%02" PRIu32, script->filename(), script->lineno(),
207                  thisFunctionIndex);
208     }
209     return;
210   }
211 
212   if (PerfBlockEnabled() && basicBlocks_.length() > 0) {
213     uint32_t thisFunctionIndex = nextFunctionIndex++;
214     uintptr_t funcStart = uintptr_t(code->raw());
215     uintptr_t funcEndInlineCode = funcStart + endInlineCode.offset();
216     uintptr_t funcEnd = funcStart + code->instructionsSize();
217 
218     // function begins with the prologue, which is located before the first
219     // basic block
220     size_t prologueSize = basicBlocks_[0].start.offset();
221 
222     if (prologueSize > 0) {
223       WriteEntry(lock, funcStart, prologueSize,
224                  "%s:%u: Func%02" PRIu32 "-Prologue", script->filename(),
225                  script->lineno(), thisFunctionIndex);
226     }
227 
228     uintptr_t cur = funcStart + prologueSize;
229     for (uint32_t i = 0; i < basicBlocks_.length(); i++) {
230       Record& r = basicBlocks_[i];
231 
232       uintptr_t blockStart = funcStart + r.start.offset();
233       uintptr_t blockEnd = funcStart + r.end.offset();
234 
235       MOZ_ASSERT(cur <= blockStart);
236       if (cur < blockStart) {
237         WriteEntry(lock, cur, blockStart - cur,
238                    "%s:%u: Func%02" PRIu32 "-Block?", script->filename(),
239                    script->lineno(), thisFunctionIndex);
240       }
241       cur = blockEnd;
242 
243       size_t size = blockEnd - blockStart;
244 
245       if (size > 0) {
246         WriteEntry(lock, blockStart, size,
247                    "%s:%u:%u: Func%02" PRIu32 "d-Block%" PRIu32, r.filename,
248                    r.lineNumber, r.columnNumber, thisFunctionIndex, r.id);
249       }
250     }
251 
252     MOZ_ASSERT(cur <= funcEndInlineCode);
253     if (cur < funcEndInlineCode) {
254       WriteEntry(lock, cur, funcEndInlineCode - cur,
255                  "%s:%u: Func%02" PRIu32 "-Epilogue", script->filename(),
256                  script->lineno(), thisFunctionIndex);
257     }
258 
259     MOZ_ASSERT(funcEndInlineCode <= funcEnd);
260     if (funcEndInlineCode < funcEnd) {
261       WriteEntry(lock, funcEndInlineCode, funcEnd - funcEndInlineCode,
262                  "%s:%u: Func%02" PRIu32 "-OOL", script->filename(),
263                  script->lineno(), thisFunctionIndex);
264     }
265   }
266 }
267 
writePerfSpewerBaselineProfile(JSScript * script,JitCode * code)268 void js::jit::writePerfSpewerBaselineProfile(JSScript* script, JitCode* code) {
269   if (!PerfEnabled()) {
270     return;
271   }
272 
273   size_t size = code->instructionsSize();
274   if (size > 0) {
275     AutoLockPerfMap lock;
276     PerfSpewer::WriteEntry(lock, reinterpret_cast<uintptr_t>(code->raw()), size,
277                            "%s:%u: Baseline", script->filename(),
278                            script->lineno());
279   }
280 }
281 
writePerfSpewerJitCodeProfile(JitCode * code,const char * msg)282 void js::jit::writePerfSpewerJitCodeProfile(JitCode* code, const char* msg) {
283   if (!code || !PerfEnabled()) {
284     return;
285   }
286 
287   size_t size = code->instructionsSize();
288   if (size > 0) {
289     AutoLockPerfMap lock;
290     PerfSpewer::WriteEntry(lock, reinterpret_cast<uintptr_t>(code->raw()), size,
291                            "%s (%p 0x%zx)", msg, code->raw(), size);
292   }
293 }
294 
writePerfSpewerWasmMap(uintptr_t base,uintptr_t size,const char * filename,const char * annotation)295 void js::jit::writePerfSpewerWasmMap(uintptr_t base, uintptr_t size,
296                                      const char* filename,
297                                      const char* annotation) {
298   if (!PerfFuncEnabled() || size == 0U) {
299     return;
300   }
301 
302   AutoLockPerfMap lock;
303   PerfSpewer::WriteEntry(lock, base, size, "%s: Function %s", filename,
304                          annotation);
305 }
306 
writePerfSpewerWasmFunctionMap(uintptr_t base,uintptr_t size,const char * filename,unsigned lineno,const char * funcName)307 void js::jit::writePerfSpewerWasmFunctionMap(uintptr_t base, uintptr_t size,
308                                              const char* filename,
309                                              unsigned lineno,
310                                              const char* funcName) {
311   if (!PerfFuncEnabled() || size == 0U) {
312     return;
313   }
314 
315   AutoLockPerfMap lock;
316   PerfSpewer::WriteEntry(lock, base, size, "%s:%u: Function %s", filename,
317                          lineno, funcName);
318 }
319 
320 #endif  // defined (JS_ION_PERF)
321