1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "jit/PerfSpewer.h"
8 
9 #include "mozilla/IntegerPrintfMacros.h"
10 #include "mozilla/SizePrintfMacros.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 /data/local/tmp/
29 // 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 
57 static bool
openPerfMap(const char * dir)58 openPerfMap(const char* dir)
59 {
60     const ssize_t bufferSize = 256;
61     char filenameBuffer[bufferSize];
62 
63     if (snprintf(filenameBuffer, bufferSize, "%sperf-%d.map", dir, getpid()) >= bufferSize)
64         return false;
65 
66     MOZ_ASSERT(!PerfFilePtr);
67     PerfFilePtr = fopen(filenameBuffer, "a");
68 
69     if (!PerfFilePtr)
70         return false;
71 
72     return true;
73 }
74 
75 void
CheckPerf()76 js::jit::CheckPerf() {
77     if (!PerfChecked) {
78         const char* env = getenv("IONPERF");
79         if (env == nullptr) {
80             PerfMode = PERF_MODE_NONE;
81             fprintf(stderr, "Warning: JIT perf reporting requires IONPERF set to \"block\" or \"func\". ");
82             fprintf(stderr, "Perf mapping will be deactivated.\n");
83         } else if (!strcmp(env, "none")) {
84             PerfMode = PERF_MODE_NONE;
85         } else if (!strcmp(env, "block")) {
86             PerfMode = PERF_MODE_BLOCK;
87         } else if (!strcmp(env, "func")) {
88             PerfMode = PERF_MODE_FUNC;
89         } else {
90             fprintf(stderr, "Use IONPERF=func to record at function granularity\n");
91             fprintf(stderr, "Use IONPERF=block to record at basic block granularity\n");
92             fprintf(stderr, "\n");
93             fprintf(stderr, "Be advised that using IONPERF will cause all scripts\n");
94             fprintf(stderr, "to be leaked.\n");
95             exit(0);
96         }
97 
98         if (PerfMode != PERF_MODE_NONE) {
99             PerfMutex = js_new<js::Mutex>(mutexid::PerfSpewer);
100             if (!PerfMutex)
101                 MOZ_CRASH("failed to allocate PerfMutex");
102 
103             if (openPerfMap(PERF_SPEW_DIR)) {
104                 PerfChecked = true;
105                 return;
106             }
107 
108 #if defined(__ANDROID__)
109             if (openPerfMap(PERF_SPEW_DIR_2)) {
110                 PerfChecked = true;
111                 return;
112             }
113 #endif
114             fprintf(stderr, "Failed to open perf map file.  Disabling IONPERF.\n");
115             PerfMode = PERF_MODE_NONE;
116         }
117         PerfChecked = true;
118     }
119 }
120 
121 bool
PerfBlockEnabled()122 js::jit::PerfBlockEnabled() {
123     MOZ_ASSERT(PerfMode);
124     return PerfMode == PERF_MODE_BLOCK;
125 }
126 
127 bool
PerfFuncEnabled()128 js::jit::PerfFuncEnabled() {
129     MOZ_ASSERT(PerfMode);
130     return PerfMode == PERF_MODE_FUNC;
131 }
132 
133 static bool
lockPerfMap(void)134 lockPerfMap(void)
135 {
136     if (!PerfEnabled())
137         return false;
138 
139     PerfMutex->lock();
140 
141     MOZ_ASSERT(PerfFilePtr);
142     return true;
143 }
144 
145 static void
unlockPerfMap()146 unlockPerfMap()
147 {
148     MOZ_ASSERT(PerfFilePtr);
149     fflush(PerfFilePtr);
150     PerfMutex->unlock();
151 }
152 
153 uint32_t PerfSpewer::nextFunctionIndex = 0;
154 
155 bool
startBasicBlock(MBasicBlock * blk,MacroAssembler & masm)156 PerfSpewer::startBasicBlock(MBasicBlock* blk,
157                             MacroAssembler& masm)
158 {
159     if (!PerfBlockEnabled())
160         return true;
161 
162     const char* filename = blk->info().script()->filename();
163     unsigned lineNumber, columnNumber;
164     if (blk->pc()) {
165         lineNumber = PCToLineNumber(blk->info().script(),
166                                     blk->pc(),
167                                     &columnNumber);
168     } else {
169         lineNumber = 0;
170         columnNumber = 0;
171     }
172     Record r(filename, lineNumber, columnNumber, blk->id());
173     masm.bind(&r.start);
174     return basicBlocks_.append(r);
175 }
176 
177 bool
endBasicBlock(MacroAssembler & masm)178 PerfSpewer::endBasicBlock(MacroAssembler& masm)
179 {
180     if (!PerfBlockEnabled())
181         return true;
182 
183     masm.bind(&basicBlocks_.back().end);
184     return true;
185 }
186 
187 bool
noteEndInlineCode(MacroAssembler & masm)188 PerfSpewer::noteEndInlineCode(MacroAssembler& masm)
189 {
190     if (!PerfBlockEnabled())
191         return true;
192 
193     masm.bind(&endInlineCode);
194     return true;
195 }
196 
197 void
writeProfile(JSScript * script,JitCode * code,MacroAssembler & masm)198 PerfSpewer::writeProfile(JSScript* script,
199                          JitCode* code,
200                          MacroAssembler& masm)
201 {
202     if (PerfFuncEnabled()) {
203         if (!lockPerfMap())
204             return;
205 
206         uint32_t thisFunctionIndex = nextFunctionIndex++;
207 
208         size_t size = code->instructionsSize();
209         if (size > 0) {
210             fprintf(PerfFilePtr, "%p %" PRIxSIZE " %s:%" PRIuSIZE ": Func%02d\n",
211                     code->raw(),
212                     size,
213                     script->filename(),
214                     script->lineno(),
215                     thisFunctionIndex);
216         }
217         unlockPerfMap();
218         return;
219     }
220 
221     if (PerfBlockEnabled() && basicBlocks_.length() > 0) {
222         if (!lockPerfMap())
223             return;
224 
225         uint32_t thisFunctionIndex = nextFunctionIndex++;
226         uintptr_t funcStart = uintptr_t(code->raw());
227         uintptr_t funcEndInlineCode = funcStart + endInlineCode.offset();
228         uintptr_t funcEnd = funcStart + code->instructionsSize();
229 
230         // function begins with the prologue, which is located before the first basic block
231         size_t prologueSize = basicBlocks_[0].start.offset();
232 
233         if (prologueSize > 0) {
234             fprintf(PerfFilePtr, "%" PRIxSIZE " %" PRIxSIZE " %s:%" PRIuSIZE ": Func%02d-Prologue\n",
235                     funcStart, prologueSize, script->filename(), script->lineno(), thisFunctionIndex);
236         }
237 
238         uintptr_t cur = funcStart + prologueSize;
239         for (uint32_t i = 0; i < basicBlocks_.length(); i++) {
240             Record& r = basicBlocks_[i];
241 
242             uintptr_t blockStart = funcStart + r.start.offset();
243             uintptr_t blockEnd = funcStart + r.end.offset();
244 
245             MOZ_ASSERT(cur <= blockStart);
246             if (cur < blockStart) {
247                 fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxPTR " %s:%" PRIuSIZE ": Func%02d-Block?\n",
248                         cur, blockStart - cur,
249                         script->filename(), script->lineno(),
250                         thisFunctionIndex);
251             }
252             cur = blockEnd;
253 
254             size_t size = blockEnd - blockStart;
255 
256             if (size > 0) {
257                 fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxSIZE " %s:%d:%d: Func%02d-Block%d\n",
258                         blockStart, size,
259                         r.filename, r.lineNumber, r.columnNumber,
260                         thisFunctionIndex, r.id);
261             }
262         }
263 
264         MOZ_ASSERT(cur <= funcEndInlineCode);
265         if (cur < funcEndInlineCode) {
266             fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxPTR " %s:%" PRIuSIZE ": Func%02d-Epilogue\n",
267                     cur, funcEndInlineCode - cur,
268                     script->filename(), script->lineno(),
269                     thisFunctionIndex);
270         }
271 
272         MOZ_ASSERT(funcEndInlineCode <= funcEnd);
273         if (funcEndInlineCode < funcEnd) {
274             fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxPTR " %s:%" PRIuSIZE ": Func%02d-OOL\n",
275                     funcEndInlineCode, funcEnd - funcEndInlineCode,
276                     script->filename(), script->lineno(),
277                     thisFunctionIndex);
278         }
279 
280         unlockPerfMap();
281         return;
282     }
283 }
284 
285 void
writePerfSpewerBaselineProfile(JSScript * script,JitCode * code)286 js::jit::writePerfSpewerBaselineProfile(JSScript* script, JitCode* code)
287 {
288     if (!PerfEnabled())
289         return;
290 
291     if (!lockPerfMap())
292         return;
293 
294     size_t size = code->instructionsSize();
295     if (size > 0) {
296         fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxSIZE " %s:%" PRIuSIZE ": Baseline\n",
297                 reinterpret_cast<uintptr_t>(code->raw()),
298                 size, script->filename(), script->lineno());
299     }
300 
301     unlockPerfMap();
302 }
303 
304 void
writePerfSpewerJitCodeProfile(JitCode * code,const char * msg)305 js::jit::writePerfSpewerJitCodeProfile(JitCode* code, const char* msg)
306 {
307     if (!code || !PerfEnabled())
308         return;
309 
310     if (!lockPerfMap())
311         return;
312 
313     size_t size = code->instructionsSize();
314     if (size > 0) {
315         fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxSIZE " %s (%p 0x%" PRIxSIZE ")\n",
316                 reinterpret_cast<uintptr_t>(code->raw()),
317                 size, msg, code->raw(), size);
318     }
319 
320     unlockPerfMap();
321 }
322 
323 void
writePerfSpewerWasmFunctionMap(uintptr_t base,uintptr_t size,const char * filename,unsigned lineno,unsigned colIndex,const char * funcName)324 js::jit::writePerfSpewerWasmFunctionMap(uintptr_t base, uintptr_t size,
325                                          const char* filename, unsigned lineno, unsigned colIndex,
326                                          const char* funcName)
327 {
328     if (!PerfFuncEnabled() || size == 0U)
329         return;
330 
331     if (!lockPerfMap())
332         return;
333 
334     fprintf(PerfFilePtr, "%" PRIxPTR " %" PRIxPTR " %s:%u:%u: Function %s\n",
335             base, size, filename, lineno, colIndex, funcName);
336 
337     unlockPerfMap();
338 }
339 
340 #endif // defined (JS_ION_PERF)
341