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__anon84992c8c0111::AutoLockPerfMap137 AutoLockPerfMap() {
138 if (!PerfEnabled()) {
139 return;
140 }
141 PerfMutex->lock();
142 MOZ_ASSERT(PerfFilePtr);
143 }
~AutoLockPerfMap__anon84992c8c0111::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