PerfSpewer.cpp (33997B)
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 "mozilla/IntegerPrintfMacros.h" 8 #include "mozilla/Printf.h" 9 10 #if defined(JS_ION_PERF) && defined(XP_UNIX) 11 # include <fcntl.h> 12 # include <sys/mman.h> 13 # include <sys/stat.h> 14 # include <unistd.h> 15 #endif 16 17 #if defined(JS_ION_PERF) && defined(XP_LINUX) && !defined(ANDROID) && \ 18 defined(__GLIBC__) 19 # include <dlfcn.h> 20 # include <sys/syscall.h> 21 # include <sys/types.h> 22 # include <unistd.h> 23 # define gettid() static_cast<pid_t>(syscall(__NR_gettid)) 24 #endif 25 26 #if defined(JS_ION_PERF) && (defined(ANDROID) || defined(XP_DARWIN)) 27 # include <limits.h> 28 # include <stdlib.h> 29 # include <unistd.h> 30 char* get_current_dir_name() { 31 char* buffer = (char*)malloc(PATH_MAX * sizeof(char)); 32 if (buffer == nullptr) { 33 return nullptr; 34 } 35 36 if (getcwd(buffer, PATH_MAX) == nullptr) { 37 free(buffer); 38 return nullptr; 39 } 40 41 return buffer; 42 } 43 #endif 44 45 #if defined(JS_ION_PERF) && defined(XP_DARWIN) 46 # include <pthread.h> 47 # include <unistd.h> 48 49 pid_t gettid_pthread() { 50 uint64_t tid; 51 if (pthread_threadid_np(nullptr, &tid) != 0) { 52 return 0; 53 } 54 // Truncate the tid to 32 bits. macOS thread IDs are usually small enough. 55 // And even if we do end up truncating, it doesn't matter much for Jitdump 56 // as long as the process ID is correct. 57 return pid_t(tid); 58 } 59 # define gettid() gettid_pthread() 60 #endif 61 62 #include "jit/PerfSpewer.h" 63 64 #include <atomic> 65 66 #include "jit/BaselineFrameInfo.h" 67 #include "jit/CacheIR.h" 68 #include "jit/Jitdump.h" 69 #include "jit/JitSpewer.h" 70 #include "jit/LIR.h" 71 #include "jit/MIR-wasm.h" 72 #include "jit/MIR.h" 73 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset 74 #include "js/Printf.h" 75 #include "vm/BytecodeUtil.h" 76 #include "vm/MutexIDs.h" 77 78 #ifdef XP_WIN 79 # include "util/WindowsWrapper.h" 80 # include <codecvt> 81 # include <evntprov.h> 82 # include <locale> 83 # include <string> 84 85 const GUID PROVIDER_JSCRIPT9 = { 86 0x57277741, 87 0x3638, 88 0x4a4b, 89 {0xbd, 0xba, 0x0a, 0xc6, 0xe4, 0x5d, 0xa5, 0x6c}}; 90 const EVENT_DESCRIPTOR MethodLoad = {0x9, 0x0, 0x0, 0x4, 0xa, 0x1, 0x1}; 91 92 static REGHANDLE sETWRegistrationHandle = NULL; 93 94 static std::atomic<bool> etwCollection = false; 95 #endif 96 97 using namespace js; 98 using namespace js::jit; 99 100 enum class PerfModeType { None, Function, Source, IR, IROperands, IRGraph }; 101 102 static std::atomic<PerfModeType> PerfMode = PerfModeType::None; 103 104 // Mutex to guard access to the profiler vectors and jitdump file if perf 105 // profiling is enabled. 106 MOZ_RUNINIT static js::Mutex PerfMutex(mutexid::PerfSpewer); 107 108 #ifdef JS_ION_PERF 109 MOZ_RUNINIT static UniqueChars spew_dir; 110 static FILE* JitDumpFilePtr = nullptr; 111 static void* mmap_address = nullptr; 112 static bool IsPerfProfiling() { return JitDumpFilePtr != nullptr; } 113 #endif 114 115 AutoLockPerfSpewer::AutoLockPerfSpewer() { PerfMutex.lock(); } 116 117 AutoLockPerfSpewer::~AutoLockPerfSpewer() { PerfMutex.unlock(); } 118 119 #ifdef JS_ION_PERF 120 static uint64_t GetMonotonicTimestamp() { 121 using mozilla::TimeStamp; 122 # ifdef XP_LINUX 123 return TimeStamp::Now().RawClockMonotonicNanosecondsSinceBoot(); 124 # elif XP_WIN 125 return TimeStamp::Now().RawQueryPerformanceCounterValue(); 126 # elif XP_DARWIN 127 return TimeStamp::Now().RawMachAbsoluteTimeNanoseconds(); 128 # else 129 MOZ_CRASH("no timestamp"); 130 # endif 131 } 132 133 // values are from /usr/include/elf.h 134 static uint32_t GetMachineEncoding() { 135 # if defined(JS_CODEGEN_X86) 136 return 3; // EM_386 137 # elif defined(JS_CODEGEN_X64) 138 return 62; // EM_X86_64 139 # elif defined(JS_CODEGEN_ARM) 140 return 40; // EM_ARM 141 # elif defined(JS_CODEGEN_ARM64) 142 return 183; // EM_AARCH64 143 # elif defined(JS_CODEGEN_MIPS64) 144 return 8; // EM_MIPS 145 # else 146 return 0; // Unsupported 147 # endif 148 } 149 150 static void WriteToJitDumpFile(const void* addr, uint32_t size, 151 AutoLockPerfSpewer& lock) { 152 MOZ_RELEASE_ASSERT(JitDumpFilePtr); 153 size_t rv = fwrite(addr, 1, size, JitDumpFilePtr); 154 MOZ_RELEASE_ASSERT(rv == size); 155 } 156 157 static void WriteJitDumpDebugEntry(uint64_t addr, const char* filename, 158 uint32_t lineno, uint32_t discrim, 159 AutoLockPerfSpewer& lock) { 160 JitDumpDebugEntry entry = {addr, lineno, discrim}; 161 WriteToJitDumpFile(&entry, sizeof(entry), lock); 162 WriteToJitDumpFile(filename, strlen(filename) + 1, lock); 163 } 164 165 static void writeJitDumpHeader(AutoLockPerfSpewer& lock) { 166 JitDumpHeader header = {}; 167 header.magic = 0x4A695444; 168 header.version = 1; 169 header.total_size = sizeof(header); 170 header.elf_mach = GetMachineEncoding(); 171 header.pad1 = 0; 172 header.pid = getpid(); 173 header.timestamp = GetMonotonicTimestamp(); 174 header.flags = 0; 175 176 WriteToJitDumpFile(&header, sizeof(header), lock); 177 } 178 179 static bool openJitDump() { 180 if (JitDumpFilePtr) { 181 return true; 182 } 183 AutoLockPerfSpewer lock; 184 185 const ssize_t bufferSize = 256; 186 char filenameBuffer[bufferSize]; 187 188 // We want to write absolute filenames into the debug info or else we get the 189 // filenames in the perf report 190 if (getenv("PERF_SPEW_DIR")) { 191 char* env_dir = getenv("PERF_SPEW_DIR"); 192 if (env_dir[0] == '/') { 193 spew_dir = JS_smprintf("%s", env_dir); 194 } else { 195 const char* dir = get_current_dir_name(); 196 if (!dir) { 197 fprintf(stderr, "couldn't get current dir name\n"); 198 return false; 199 } 200 spew_dir = JS_smprintf("%s/%s", dir, env_dir); 201 free((void*)dir); 202 } 203 } else { 204 fprintf(stderr, "Please define PERF_SPEW_DIR as an output directory.\n"); 205 return false; 206 } 207 208 if (SprintfBuf(filenameBuffer, bufferSize, "%s/jit-%d.dump", spew_dir.get(), 209 getpid()) >= bufferSize) { 210 return false; 211 } 212 213 MOZ_ASSERT(!JitDumpFilePtr); 214 215 int fd = open(filenameBuffer, O_CREAT | O_TRUNC | O_RDWR, 0666); 216 JitDumpFilePtr = fdopen(fd, "w+"); 217 218 if (!JitDumpFilePtr) { 219 return false; 220 } 221 222 # ifdef XP_LINUX 223 // We need to mmap the jitdump file for perf to find it. 224 long page_size = sysconf(_SC_PAGESIZE); 225 int prot = PROT_READ | PROT_EXEC; 226 // The mmap call fails on some Android devices if PROT_EXEC is specified, and 227 // it does not appear to be required by simpleperf, so omit it on Android. 228 # ifdef ANDROID 229 prot &= ~PROT_EXEC; 230 # endif 231 mmap_address = mmap(nullptr, page_size, prot, MAP_PRIVATE, fd, 0); 232 if (mmap_address == MAP_FAILED) { 233 PerfMode = PerfModeType::None; 234 return false; 235 } 236 # endif 237 238 writeJitDumpHeader(lock); 239 return true; 240 } 241 242 static void CheckPerf() { 243 static bool PerfChecked = false; 244 245 if (!PerfChecked) { 246 const char* env = getenv("IONPERF"); 247 if (env == nullptr) { 248 PerfMode = PerfModeType::None; 249 } else if (!strcmp(env, "src")) { 250 PerfMode = PerfModeType::Source; 251 } else if (!strcmp(env, "ir")) { 252 PerfMode = PerfModeType::IR; 253 } else if (!strcmp(env, "ir-ops")) { 254 # ifdef JS_JITSPEW 255 PerfMode = PerfModeType::IROperands; 256 # else 257 fprintf(stderr, 258 "Warning: IONPERF=ir-ops requires --enable-jitspew to be " 259 "enabled, defaulting to IONPERF=ir\n"); 260 PerfMode = PerfModeType::IR; 261 # endif 262 } else if (!strcmp(env, "ir-graph")) { 263 # ifdef JS_JITSPEW 264 PerfMode = PerfModeType::IRGraph; 265 # else 266 fprintf(stderr, 267 "Warning: IONPERF=ir-graph requires --enable-jitspew to be " 268 "enabled, defaulting to IONPERF=ir\n"); 269 PerfMode = PerfModeType::IR; 270 # endif 271 } else if (!strcmp(env, "func")) { 272 PerfMode = PerfModeType::Function; 273 } else { 274 fprintf(stderr, "Use IONPERF=func to record at function granularity\n"); 275 fprintf(stderr, 276 "Use IONPERF=ir to record and annotate assembly with IR\n"); 277 # ifdef JS_JITSPEW 278 fprintf(stderr, 279 "Use IONPERF=ir-ops to record and annotate assembly with IR that " 280 "shows operands\n"); 281 fprintf(stderr, 282 "Use IONPERF=ir-graph to record structured IR graphs for " 283 "visualization\n"); 284 # endif 285 fprintf(stderr, 286 "Use IONPERF=src to record and annotate assembly with source, if " 287 "available locally\n"); 288 exit(0); 289 } 290 291 if (PerfMode != PerfModeType::None) { 292 if (openJitDump()) { 293 PerfChecked = true; 294 return; 295 } 296 297 fprintf(stderr, "Failed to open perf map file. Disabling IONPERF.\n"); 298 PerfMode = PerfModeType::None; 299 } 300 PerfChecked = true; 301 } 302 } 303 #endif 304 305 #ifdef XP_WIN 306 void NTAPI ETWEnableCallback(LPCGUID aSourceId, ULONG aIsEnabled, UCHAR aLevel, 307 ULONGLONG aMatchAnyKeyword, 308 ULONGLONG aMatchAllKeyword, 309 PEVENT_FILTER_DESCRIPTOR aFilterData, 310 PVOID aCallbackContext) { 311 // This is called on a CRT worker thread. This means this might race with 312 // our main thread, but that is okay. 313 etwCollection = aIsEnabled; 314 PerfMode = aIsEnabled ? PerfModeType::Function : PerfModeType::None; 315 } 316 317 void RegisterETW() { 318 static bool sHasRegisteredETW = false; 319 if (!sHasRegisteredETW) { 320 if (getenv("ETW_ENABLED")) { 321 EventRegister(&PROVIDER_JSCRIPT9, // GUID that identifies the provider 322 ETWEnableCallback, // Callback for enabling collection 323 NULL, // Context not used 324 &sETWRegistrationHandle // Used when calling EventWrite 325 // and EventUnregister 326 ); 327 } 328 sHasRegisteredETW = true; 329 } 330 } 331 #endif 332 333 /* static */ 334 void PerfSpewer::Init() { 335 #ifdef JS_ION_PERF 336 CheckPerf(); 337 #endif 338 #ifdef XP_WIN 339 RegisterETW(); 340 #endif 341 } 342 343 static void DisablePerfSpewer(AutoLockPerfSpewer& lock) { 344 fprintf(stderr, "Warning: Disabling PerfSpewer."); 345 346 #ifdef XP_WIN 347 etwCollection = false; 348 #endif 349 PerfMode = PerfModeType::None; 350 #ifdef JS_ION_PERF 351 long page_size = sysconf(_SC_PAGESIZE); 352 munmap(mmap_address, page_size); 353 fclose(JitDumpFilePtr); 354 JitDumpFilePtr = nullptr; 355 #endif 356 } 357 358 static void DisablePerfSpewer() { 359 AutoLockPerfSpewer lock; 360 DisablePerfSpewer(lock); 361 } 362 363 static bool PerfSrcEnabled() { return PerfMode == PerfModeType::Source; } 364 365 #ifdef JS_JITSPEW 366 static bool PerfIROpsEnabled() { return PerfMode == PerfModeType::IROperands; } 367 static bool PerfIRGraphEnabled() { return PerfMode == PerfModeType::IRGraph; } 368 #endif 369 370 static bool PerfIREnabled() { 371 return (PerfMode == PerfModeType::IRGraph) || 372 (PerfMode == PerfModeType::IROperands) || 373 (PerfMode == PerfModeType::IR); 374 } 375 376 bool js::jit::PerfEnabled() { return PerfMode != PerfModeType::None; } 377 378 void InlineCachePerfSpewer::recordInstruction(MacroAssembler& masm, 379 const CacheOp& op) { 380 if (!PerfIREnabled()) { 381 return; 382 } 383 AutoLockPerfSpewer lock; 384 385 recordOpcode(masm.currentOffset() - startOffset_, static_cast<uint32_t>(op)); 386 } 387 388 #define CHECK_RETURN(x) \ 389 if (!(x)) { \ 390 disable(); \ 391 return; \ 392 } 393 394 void IonPerfSpewer::disable() { 395 #ifdef JS_JITSPEW 396 if (graphSpewer_) { 397 graphPrinter_.finish(); 398 graphSpewer_ = nullptr; 399 } 400 #endif 401 PerfSpewer::disable(); 402 } 403 404 void IonPerfSpewer::startRecording(const wasm::CodeMetadata* wasmCodeMeta) { 405 PerfSpewer::startRecording(); 406 #ifdef JS_JITSPEW 407 if (PerfIRGraphEnabled()) { 408 graphPrinter_.init(irFile_); 409 graphSpewer_ = MakeUnique<GraphSpewer>(graphPrinter_, wasmCodeMeta); 410 if (!graphSpewer_) { 411 disable(); 412 } 413 graphSpewer_->begin(); 414 graphSpewer_->beginAnonFunction(); 415 } 416 #endif 417 } 418 419 void IonPerfSpewer::endRecording() { 420 #ifdef JS_JITSPEW 421 if (graphSpewer_) { 422 graphSpewer_->endFunction(); 423 graphSpewer_->end(); 424 graphPrinter_.finish(); 425 graphSpewer_ = nullptr; 426 } 427 #endif 428 PerfSpewer::endRecording(); 429 } 430 431 void IonPerfSpewer::recordPass(const char* pass, MIRGraph* graph, 432 BacktrackingAllocator* ra) { 433 #ifdef JS_JITSPEW 434 if (PerfIRGraphEnabled() && graphSpewer_) { 435 graphSpewer_->spewPass(pass, graph, ra); 436 } 437 #endif 438 } 439 440 void IonPerfSpewer::recordInstruction(MacroAssembler& masm, LInstruction* ins) { 441 uint32_t offset = masm.currentOffset() - startOffset_; 442 443 if (PerfSrcEnabled()) { 444 uint32_t line = 0; 445 uint32_t column = 0; 446 if (MDefinition* mir = ins->mirRaw()) { 447 jsbytecode* pc = mir->trackedSite()->pc(); 448 JSScript* script = mir->trackedSite()->script(); 449 JS::LimitedColumnNumberOneOrigin colno; 450 line = PCToLineNumber(script, pc, &colno); 451 column = colno.oneOriginValue(); 452 } 453 454 if (!debugInfo_.emplaceBack(offset, line, column)) { 455 disable(); 456 } 457 return; 458 } 459 460 if (!PerfIREnabled()) { 461 return; 462 } 463 464 #ifdef JS_JITSPEW 465 if (PerfIRGraphEnabled()) { 466 if (!debugInfo_.emplaceBack(offset, ins->id(), 0)) { 467 disable(); 468 } 469 return; 470 } 471 #endif 472 473 LNode::Opcode op = ins->op(); 474 UniqueChars opcodeStr; 475 476 #ifdef JS_JITSPEW 477 if (PerfIROpsEnabled()) { 478 Sprinter buf; 479 CHECK_RETURN(buf.init()); 480 buf.put(LIRCodeName(op)); 481 ins->printOperands(buf); 482 opcodeStr = buf.release(); 483 } 484 #endif 485 486 recordOpcode(offset, static_cast<uint32_t>(op), std::move(opcodeStr)); 487 } 488 489 #ifdef JS_JITSPEW 490 static void PrintStackValue(JSContext* maybeCx, StackValue* stackVal, 491 CompilerFrameInfo& frame, Sprinter& buf) { 492 switch (stackVal->kind()) { 493 /****** Constant ******/ 494 case StackValue::Constant: { 495 js::Value constantVal = stackVal->constant(); 496 if (constantVal.isInt32()) { 497 buf.printf("%d", constantVal.toInt32()); 498 } else if (constantVal.isObjectOrNull()) { 499 buf.printf("obj:%p", constantVal.toObjectOrNull()); 500 } else if (constantVal.isString()) { 501 if (maybeCx) { 502 buf.put("str:"); 503 buf.putString(maybeCx, constantVal.toString()); 504 } else { 505 buf.put("str"); 506 } 507 } else if (constantVal.isNumber()) { 508 buf.printf("num:%f", constantVal.toNumber()); 509 } else if (constantVal.isSymbol()) { 510 if (maybeCx) { 511 buf.put("sym:"); 512 constantVal.toSymbol()->dump(buf); 513 } else { 514 buf.put("sym"); 515 } 516 } else { 517 buf.printf("raw:%" PRIx64, constantVal.asRawBits()); 518 } 519 } break; 520 /****** Register ******/ 521 case StackValue::Register: { 522 Register reg = stackVal->reg().payloadOrValueReg(); 523 buf.put(reg.name()); 524 } break; 525 /****** Stack ******/ 526 case StackValue::Stack: 527 buf.put("stack"); 528 break; 529 /****** ThisSlot ******/ 530 case StackValue::ThisSlot: { 531 # ifdef JS_HAS_HIDDEN_SP 532 buf.put("this"); 533 # else 534 Address addr = frame.addressOfThis(); 535 buf.printf("this:%s(%d)", addr.base.name(), addr.offset); 536 # endif 537 } break; 538 /****** LocalSlot ******/ 539 case StackValue::LocalSlot: 540 buf.printf("local:%u", stackVal->localSlot()); 541 break; 542 /****** ArgSlot ******/ 543 case StackValue::ArgSlot: 544 buf.printf("arg:%u", stackVal->argSlot()); 545 break; 546 547 default: 548 MOZ_CRASH("Unexpected kind"); 549 break; 550 } 551 } 552 #endif 553 554 [[nodiscard]] bool WasmBaselinePerfSpewer::needsToRecordInstruction() const { 555 return PerfIREnabled() || PerfSrcEnabled(); 556 } 557 558 void WasmBaselinePerfSpewer::recordInstruction(MacroAssembler& masm, 559 const wasm::OpBytes& op) { 560 MOZ_ASSERT(needsToRecordInstruction()); 561 562 recordOpcode(masm.currentOffset() - startOffset_, op.toPacked()); 563 } 564 565 void BaselinePerfSpewer::recordInstruction(MacroAssembler& masm, jsbytecode* pc, 566 JSScript* script, 567 CompilerFrameInfo& frame) { 568 uint32_t offset = masm.currentOffset() - startOffset_; 569 if (PerfSrcEnabled()) { 570 JS::LimitedColumnNumberOneOrigin colno; 571 uint32_t line = PCToLineNumber(script, pc, &colno); 572 uint32_t column = colno.oneOriginValue(); 573 if (!debugInfo_.emplaceBack(offset, line, column)) { 574 disable(); 575 } 576 return; 577 } 578 579 if (!PerfIREnabled()) { 580 return; 581 } 582 583 JSOp op = JSOp(*pc); 584 UniqueChars opcodeStr; 585 586 #ifdef JS_JITSPEW 587 if (PerfIROpsEnabled()) { 588 JSScript* script = frame.script; 589 unsigned numOperands = js::StackUses(op, pc); 590 591 JSContext* maybeCx = TlsContext.get(); 592 Sprinter buf(maybeCx); 593 CHECK_RETURN(buf.init()); 594 buf.put(js::CodeName(op)); 595 596 if (maybeCx) { 597 switch (op) { 598 case JSOp::SetName: 599 case JSOp::SetGName: 600 case JSOp::BindName: 601 case JSOp::BindUnqualifiedName: 602 case JSOp::BindUnqualifiedGName: 603 case JSOp::GetName: 604 case JSOp::GetGName: { 605 // Emit the name used for these ops 606 Rooted<PropertyName*> name(maybeCx, script->getName(pc)); 607 buf.put(" "); 608 buf.putString(maybeCx, name); 609 } break; 610 default: 611 break; 612 } 613 614 // Output should be "JSOp (operand1), (operand2), ..." 615 for (unsigned i = 1; i <= numOperands; i++) { 616 buf.put(" ("); 617 StackValue* stackVal = frame.peek(-int(i)); 618 PrintStackValue(maybeCx, stackVal, frame, buf); 619 620 if (i < numOperands) { 621 buf.put("),"); 622 } else { 623 buf.put(")"); 624 } 625 } 626 } 627 opcodeStr = buf.release(); 628 } 629 #endif 630 631 recordOpcode(offset, static_cast<uint32_t>(op), std::move(opcodeStr)); 632 } 633 634 const char* BaselinePerfSpewer::CodeName(uint32_t op) { 635 return js::CodeName(static_cast<JSOp>(op)); 636 } 637 638 const char* BaselineInterpreterPerfSpewer::CodeName(uint32_t op) { 639 return js::CodeName(static_cast<JSOp>(op)); 640 } 641 642 const char* IonPerfSpewer::CodeName(uint32_t op) { 643 return js::jit::LIRCodeName(static_cast<LNode::Opcode>(op)); 644 } 645 646 const char* IonPerfSpewer::IRFileExtension() { 647 #ifdef JS_JITSPEW 648 if (PerfIRGraphEnabled()) { 649 return ".iongraph.json"; 650 } 651 #endif 652 return ".txt"; 653 } 654 655 const char* WasmBaselinePerfSpewer::CodeName(uint32_t op) { 656 return wasm::OpBytes::fromPacked(op).toString(); 657 } 658 659 const char* InlineCachePerfSpewer::CodeName(uint32_t op) { 660 return js::jit::CacheIRCodeName(static_cast<CacheOp>(op)); 661 } 662 663 void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, JitCode* code, 664 AutoLockPerfSpewer& lock) { 665 CollectJitCodeInfo(function_name, reinterpret_cast<void*>(code->raw()), 666 code->instructionsSize(), lock); 667 } 668 669 void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, void* code_addr, 670 uint64_t code_size, 671 AutoLockPerfSpewer& lock) { 672 #ifdef JS_ION_PERF 673 static uint64_t codeIndex = 1; 674 675 if (IsPerfProfiling()) { 676 JitDumpLoadRecord record = {}; 677 678 record.header.id = JIT_CODE_LOAD; 679 record.header.total_size = 680 sizeof(record) + strlen(function_name.get()) + 1 + code_size; 681 record.header.timestamp = GetMonotonicTimestamp(); 682 record.pid = getpid(); 683 record.tid = gettid(); 684 record.vma = uint64_t(code_addr); 685 record.code_addr = uint64_t(code_addr); 686 record.code_size = code_size; 687 record.code_index = codeIndex++; 688 689 WriteToJitDumpFile(&record, sizeof(record), lock); 690 WriteToJitDumpFile(function_name.get(), strlen(function_name.get()) + 1, 691 lock); 692 WriteToJitDumpFile(code_addr, code_size, lock); 693 } 694 #endif 695 #ifdef XP_WIN 696 if (etwCollection) { 697 void* scriptContextId = NULL; 698 uint32_t flags = 0; 699 uint64_t map = 0; 700 uint64_t assembly = 0; 701 uint32_t line_col = 0; 702 uint32_t method = 0; 703 704 int name_len = strlen(function_name.get()); 705 std::wstring name(name_len + 1, '\0'); 706 if (MultiByteToWideChar(CP_UTF8, 0, function_name.get(), name_len, 707 name.data(), name.size()) == 0) { 708 DisablePerfSpewer(lock); 709 return; 710 } 711 712 EVENT_DATA_DESCRIPTOR EventData[10]; 713 714 EventDataDescCreate(&EventData[0], &scriptContextId, sizeof(PVOID)); 715 EventDataDescCreate(&EventData[1], &code_addr, sizeof(PVOID)); 716 EventDataDescCreate(&EventData[2], &code_size, sizeof(unsigned __int64)); 717 EventDataDescCreate(&EventData[3], &method, sizeof(uint32_t)); 718 EventDataDescCreate(&EventData[4], &flags, sizeof(const unsigned short)); 719 EventDataDescCreate(&EventData[5], &map, sizeof(const unsigned short)); 720 EventDataDescCreate(&EventData[6], &assembly, sizeof(unsigned __int64)); 721 EventDataDescCreate(&EventData[7], &line_col, sizeof(const unsigned int)); 722 EventDataDescCreate(&EventData[8], &line_col, sizeof(const unsigned int)); 723 EventDataDescCreate(&EventData[9], name.c_str(), 724 sizeof(wchar_t) * (name.length() + 1)); 725 726 ULONG result = EventWrite( 727 sETWRegistrationHandle, // From EventRegister 728 &MethodLoad, // EVENT_DESCRIPTOR generated from the manifest 729 (ULONG)10, // Size of the array of EVENT_DATA_DESCRIPTORs 730 EventData // Array of descriptors that contain the event data 731 ); 732 733 if (result != ERROR_SUCCESS) { 734 DisablePerfSpewer(lock); 735 return; 736 } 737 } 738 #endif 739 } 740 741 void PerfSpewer::recordOffset(MacroAssembler& masm, const char* msg) { 742 if (!PerfIREnabled()) { 743 return; 744 } 745 #ifdef JS_JITSPEW 746 if (PerfIRGraphEnabled()) { 747 return; 748 } 749 #endif 750 751 UniqueChars offsetStr = DuplicateString(msg); 752 recordOpcode(masm.currentOffset() - startOffset_, std::move(offsetStr)); 753 } 754 755 void PerfSpewer::recordOpcode(uint32_t offset, uint32_t opcode) { 756 recordOpcode(offset, opcode, JS::UniqueChars(nullptr)); 757 } 758 759 void PerfSpewer::recordOpcode(uint32_t offset, uint32_t opcode, 760 JS::UniqueChars&& str) { 761 if (!irFile_) { 762 // If we don't have a file, we can't record the opcode. 763 return; 764 } 765 766 irFileLines_ += 1; 767 if (!debugInfo_.emplaceBack(offset, irFileLines_)) { 768 disable(); 769 return; 770 } 771 772 if (str.get()) { 773 fprintf(irFile_, "%s\n", str.get()); 774 } else { 775 fprintf(irFile_, "%s\n", CodeName(opcode)); 776 } 777 } 778 779 void PerfSpewer::recordOpcode(uint32_t offset, JS::UniqueChars&& str) { 780 recordOpcode(offset, 0, std::move(str)); 781 } 782 783 void PerfSpewer::saveDebugInfo(const char* filename, uintptr_t base, 784 AutoLockPerfSpewer& lock) { 785 #ifdef JS_ION_PERF 786 if (!IsPerfProfiling()) { 787 return; 788 } 789 790 JitDumpDebugRecord debug_record = {}; 791 792 uint64_t n_records = debugInfo_.length(); 793 794 debug_record.header.id = JIT_CODE_DEBUG_INFO; 795 debug_record.header.total_size = 796 sizeof(debug_record) + 797 n_records * (sizeof(JitDumpDebugEntry) + strlen(filename) + 1); 798 debug_record.header.timestamp = GetMonotonicTimestamp(); 799 debug_record.code_addr = uint64_t(base); 800 debug_record.nr_entry = n_records; 801 802 WriteToJitDumpFile(&debug_record, sizeof(debug_record), lock); 803 for (DebugEntry& entry : debugInfo_) { 804 WriteJitDumpDebugEntry(uint64_t(base) + entry.offset, filename, entry.line, 805 entry.column, lock); 806 } 807 #endif 808 } 809 810 static UniqueChars GetFunctionDesc(const char* tierName, JSContext* cx, 811 JSScript* script, 812 const char* stubName = nullptr) { 813 MOZ_ASSERT(script && tierName && cx); 814 UniqueChars funName; 815 if (script->function() && script->function()->maybePartialDisplayAtom()) { 816 funName = AtomToPrintableString( 817 cx, script->function()->maybePartialDisplayAtom()); 818 } 819 820 if (stubName) { 821 return JS_smprintf("%s: %s : %s (%s:%u:%u)", tierName, stubName, 822 funName ? funName.get() : "*", script->filename(), 823 script->lineno(), script->column().oneOriginValue()); 824 } 825 return JS_smprintf("%s: %s (%s:%u:%u)", tierName, 826 funName ? funName.get() : "*", script->filename(), 827 script->lineno(), script->column().oneOriginValue()); 828 } 829 830 void PerfSpewer::saveJitCodeDebugInfo(JSScript* script, JitCode* code, 831 AutoLockPerfSpewer& lock) { 832 MOZ_ASSERT(code); 833 834 // We should be done with the temp IR file, if we were using it. 835 MOZ_ASSERT(!irFile_); 836 837 if (PerfIREnabled()) { 838 // We should have generated a debug file to use here. 839 MOZ_ASSERT(irFileName_.get()); 840 saveDebugInfo(irFileName_.get(), uintptr_t(code->raw()), lock); 841 return; 842 } 843 844 if (!PerfSrcEnabled() || !script || !script->filename()) { 845 return; 846 } 847 saveDebugInfo(script->filename(), uintptr_t(code->raw()), lock); 848 } 849 850 void PerfSpewer::saveWasmCodeDebugInfo(uintptr_t base, 851 AutoLockPerfSpewer& lock) { 852 // We should be done with the temp IR file, if we were using it. 853 MOZ_ASSERT(!irFile_); 854 855 if (!PerfIREnabled()) { 856 return; 857 } 858 saveDebugInfo(irFileName_.get(), base, lock); 859 } 860 861 void PerfSpewer::saveJSProfile(JitCode* code, UniqueChars& desc, 862 JSScript* script) { 863 MOZ_ASSERT(PerfEnabled()); 864 MOZ_ASSERT(code && desc); 865 AutoLockPerfSpewer lock; 866 867 saveJitCodeDebugInfo(script, code, lock); 868 CollectJitCodeInfo(desc, code, lock); 869 } 870 871 void PerfSpewer::saveWasmProfile(uintptr_t base, size_t size, 872 UniqueChars& desc) { 873 MOZ_ASSERT(PerfEnabled()); 874 MOZ_ASSERT(desc); 875 AutoLockPerfSpewer lock; 876 877 saveWasmCodeDebugInfo(base, lock); 878 PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base), 879 uint64_t(size), lock); 880 } 881 882 void PerfSpewer::disable(AutoLockPerfSpewer& lock) { 883 endRecording(); 884 debugInfo_.clear(); 885 irFileName_ = UniqueChars(); 886 DisablePerfSpewer(lock); 887 } 888 889 void PerfSpewer::disable() { 890 AutoLockPerfSpewer lock; 891 disable(lock); 892 } 893 894 void PerfSpewer::startRecording(const wasm::CodeMetadata* wasmCodeMeta) { 895 MOZ_ASSERT(!irFile_ && !irFileName_); 896 897 #ifdef JS_ION_PERF 898 static uint32_t filenameCounter = 0; 899 900 if (!IsPerfProfiling() || !PerfIREnabled()) { 901 return; 902 } 903 904 AutoLockPerfSpewer lock; 905 irFileName_ = JS_smprintf("%s/jitdump-ir-%u.%u%s", spew_dir.get(), 906 filenameCounter++, getpid(), IRFileExtension()); 907 if (!irFileName_) { 908 disable(lock); 909 return; 910 } 911 912 irFile_ = fopen(irFileName_.get(), "w"); 913 if (!irFile_) { 914 disable(lock); 915 return; 916 } 917 #endif 918 } 919 920 void PerfSpewer::endRecording() { 921 if (!irFile_) { 922 return; 923 } 924 fclose(irFile_); 925 irFile_ = nullptr; 926 } 927 928 PerfSpewer::~PerfSpewer() { 929 // Close the file, if it hasn't yet. 930 endRecording(); 931 } 932 933 PerfSpewer::PerfSpewer(PerfSpewer&& other) { 934 // Can only move a PerfSpewer after endRecording(). 935 MOZ_RELEASE_ASSERT(!irFile_ && !other.irFile_); 936 debugInfo_ = std::move(other.debugInfo_); 937 irFileName_ = std::move(other.irFileName_); 938 startOffset_ = other.startOffset_; 939 } 940 941 PerfSpewer& PerfSpewer::operator=(PerfSpewer&& other) { 942 // Can only move a PerfSpewer after endRecording(). 943 MOZ_RELEASE_ASSERT(!irFile_ && !other.irFile_); 944 debugInfo_ = std::move(other.debugInfo_); 945 irFileName_ = std::move(other.irFileName_); 946 startOffset_ = other.startOffset_; 947 return *this; 948 } 949 950 IonICPerfSpewer::IonICPerfSpewer(JSScript* script, jsbytecode* pc) { 951 if (!PerfSrcEnabled()) { 952 return; 953 } 954 955 uint32_t lineno; 956 JS::LimitedColumnNumberOneOrigin colno; 957 lineno = PCToLineNumber(script, pc, &colno); 958 959 if (!debugInfo_.emplaceBack(0, lineno, colno.oneOriginValue())) { 960 disable(); 961 } 962 } 963 964 void IonICPerfSpewer::saveProfile(JSContext* cx, JSScript* script, 965 JitCode* code, const char* stubName) { 966 if (!PerfEnabled()) { 967 return; 968 } 969 UniqueChars desc = GetFunctionDesc("IonIC", cx, script, stubName); 970 PerfSpewer::saveJSProfile(code, desc, script); 971 } 972 973 void BaselineICPerfSpewer::saveProfile(JitCode* code, const char* stubName) { 974 if (!PerfEnabled()) { 975 return; 976 } 977 UniqueChars desc = JS_smprintf("BaselineIC: %s", stubName); 978 PerfSpewer::saveJSProfile(code, desc, nullptr); 979 } 980 981 void BaselinePerfSpewer::saveProfile(JSContext* cx, JSScript* script, 982 JitCode* code) { 983 if (!PerfEnabled()) { 984 return; 985 } 986 UniqueChars desc = GetFunctionDesc("Baseline", cx, script); 987 PerfSpewer::saveJSProfile(code, desc, script); 988 } 989 990 void BaselineInterpreterPerfSpewer::saveProfile(JitCode* code) { 991 if (!PerfEnabled()) { 992 return; 993 } 994 995 enum class SpewKind { Uninitialized, SingleSym, MultiSym }; 996 997 // Check which type of Baseline Interpreter Spew is requested. 998 static SpewKind kind = SpewKind::Uninitialized; 999 if (kind == SpewKind::Uninitialized) { 1000 if (getenv("IONPERF_SINGLE_BLINTERP")) { 1001 kind = SpewKind::SingleSym; 1002 } else { 1003 kind = SpewKind::MultiSym; 1004 } 1005 } 1006 1007 // For SingleSym, just emit one "BaselineInterpreter" symbol 1008 // and emit the opcodes as IR if IONPERF=ir is used. 1009 if (kind == SpewKind::SingleSym) { 1010 for (Op& entry : ops_) { 1011 recordOpcode(entry.offset, entry.opcode, std::move(entry.str)); 1012 } 1013 ops_.clear(); 1014 UniqueChars desc = DuplicateString("BaselineInterpreter"); 1015 PerfSpewer::saveJSProfile(code, desc, nullptr); 1016 return; 1017 } 1018 1019 // For MultiSym, split up each opcode into its own symbol. 1020 // No IR is emitted in this case, so we can skip PerfSpewer::saveProfile. 1021 MOZ_ASSERT(kind == SpewKind::MultiSym); 1022 for (size_t i = 1; i < ops_.length(); i++) { 1023 uintptr_t base = uintptr_t(code->raw()) + ops_[i - 1].offset; 1024 uintptr_t size = ops_[i].offset - ops_[i - 1].offset; 1025 1026 UniqueChars rangeName; 1027 if (ops_[i - 1].str) { 1028 rangeName = JS_smprintf("BlinterpOp: %s", ops_[i - 1].str.get()); 1029 } else { 1030 rangeName = JS_smprintf("BlinterpOp: %s", CodeName(ops_[i - 1].opcode)); 1031 } 1032 1033 // If rangeName is empty, we probably went OOM. 1034 if (!rangeName) { 1035 disable(); 1036 return; 1037 } 1038 1039 MOZ_ASSERT(base + size <= 1040 uintptr_t(code->raw()) + code->instructionsSize()); 1041 CollectPerfSpewerJitCodeProfile(base, size, rangeName.get()); 1042 } 1043 } 1044 1045 void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm, 1046 const JSOp& op) { 1047 if (!PerfEnabled()) { 1048 return; 1049 } 1050 1051 if (!ops_.emplaceBack(masm.currentOffset() - startOffset_, unsigned(op))) { 1052 disable(); 1053 ops_.clear(); 1054 return; 1055 } 1056 } 1057 1058 void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm, 1059 const char* name) { 1060 if (!PerfEnabled()) { 1061 return; 1062 } 1063 1064 UniqueChars desc = DuplicateString(name); 1065 if (!ops_.emplaceBack(masm.currentOffset() - startOffset_, std::move(desc))) { 1066 disable(); 1067 ops_.clear(); 1068 return; 1069 } 1070 } 1071 1072 void IonPerfSpewer::saveJSProfile(JSContext* cx, JSScript* script, 1073 JitCode* code) { 1074 if (!PerfEnabled()) { 1075 return; 1076 } 1077 UniqueChars desc = GetFunctionDesc("Ion", cx, script); 1078 PerfSpewer::saveJSProfile(code, desc, script); 1079 } 1080 1081 void IonPerfSpewer::saveWasmProfile(uintptr_t codeBase, size_t codeSize, 1082 UniqueChars& desc) { 1083 if (!PerfEnabled()) { 1084 return; 1085 } 1086 PerfSpewer::saveWasmProfile(codeBase, codeSize, desc); 1087 } 1088 1089 void WasmBaselinePerfSpewer::saveProfile(uintptr_t codeBase, size_t codeSize, 1090 UniqueChars& desc) { 1091 if (!PerfEnabled()) { 1092 return; 1093 } 1094 PerfSpewer::saveWasmProfile(codeBase, codeSize, desc); 1095 } 1096 1097 void js::jit::CollectPerfSpewerJitCodeProfile(JitCode* code, const char* msg) { 1098 if (!code || !PerfEnabled()) { 1099 return; 1100 } 1101 1102 size_t size = code->instructionsSize(); 1103 if (size > 0) { 1104 AutoLockPerfSpewer lock; 1105 1106 UniqueChars desc = JS_smprintf("%s", msg); 1107 PerfSpewer::CollectJitCodeInfo(desc, code, lock); 1108 } 1109 } 1110 1111 void js::jit::CollectPerfSpewerJitCodeProfile(uintptr_t base, uint64_t size, 1112 const char* msg) { 1113 if (!PerfEnabled()) { 1114 return; 1115 } 1116 1117 if (size > 0) { 1118 AutoLockPerfSpewer lock; 1119 1120 UniqueChars desc = JS_smprintf("%s", msg); 1121 PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base), size, 1122 lock); 1123 } 1124 } 1125 1126 void js::jit::CollectPerfSpewerWasmMap(uintptr_t base, uintptr_t size, 1127 UniqueChars&& desc) { 1128 if (size == 0U || !PerfEnabled()) { 1129 return; 1130 } 1131 AutoLockPerfSpewer lock; 1132 1133 PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base), 1134 uint64_t(size), lock); 1135 } 1136 1137 void js::jit::PerfSpewerRangeRecorder::appendEntry(UniqueChars& desc) { 1138 if (!ranges.append(std::make_pair(masm.currentOffset(), std::move(desc)))) { 1139 DisablePerfSpewer(); 1140 ranges.clear(); 1141 } 1142 } 1143 1144 void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name) { 1145 if (!PerfEnabled()) { 1146 return; 1147 } 1148 UniqueChars desc = DuplicateString(name); 1149 appendEntry(desc); 1150 } 1151 1152 void js::jit::PerfSpewerRangeRecorder::recordVMWrapperOffset(const char* name) { 1153 if (!PerfEnabled()) { 1154 return; 1155 } 1156 1157 UniqueChars desc = JS_smprintf("VMWrapper: %s", name); 1158 appendEntry(desc); 1159 } 1160 1161 void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name, 1162 JSContext* cx, 1163 JSScript* script) { 1164 if (!PerfEnabled()) { 1165 return; 1166 } 1167 UniqueChars desc = GetFunctionDesc(name, cx, script); 1168 appendEntry(desc); 1169 } 1170 1171 void js::jit::PerfSpewerRangeRecorder::collectRangesForJitCode(JitCode* code) { 1172 if (!PerfEnabled() || ranges.empty()) { 1173 return; 1174 } 1175 1176 uintptr_t basePtr = uintptr_t(code->raw()); 1177 uintptr_t offsetStart = 0; 1178 1179 for (OffsetPair& pair : ranges) { 1180 uint32_t offsetEnd = std::get<0>(pair); 1181 uintptr_t rangeSize = uintptr_t(offsetEnd - offsetStart); 1182 const char* rangeName = std::get<1>(pair).get(); 1183 1184 CollectPerfSpewerJitCodeProfile(basePtr + offsetStart, rangeSize, 1185 rangeName); 1186 offsetStart = offsetEnd; 1187 } 1188 1189 MOZ_ASSERT(offsetStart <= code->instructionsSize()); 1190 ranges.clear(); 1191 }