tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }