summaryrefslogtreecommitdiffstats
path: root/js/src/jit/PerfSpewer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/PerfSpewer.cpp')
-rw-r--r--js/src/jit/PerfSpewer.cpp1218
1 files changed, 1218 insertions, 0 deletions
diff --git a/js/src/jit/PerfSpewer.cpp b/js/src/jit/PerfSpewer.cpp
new file mode 100644
index 0000000000..a5262edefd
--- /dev/null
+++ b/js/src/jit/PerfSpewer.cpp
@@ -0,0 +1,1218 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Printf.h"
+
+#if defined(JS_ION_PERF) && defined(XP_UNIX)
+# include <fcntl.h>
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <unistd.h>
+#endif
+
+#if defined(JS_ION_PERF) && defined(XP_LINUX) && !defined(ANDROID) && \
+ defined(__GLIBC__)
+# include <dlfcn.h>
+# include <sys/syscall.h>
+# include <sys/types.h>
+# include <unistd.h>
+# define gettid() static_cast<pid_t>(syscall(__NR_gettid))
+#endif
+
+#if defined(JS_ION_PERF) && defined(XP_MACOSX)
+# include <pthread.h>
+# include <unistd.h>
+
+pid_t gettid_pthread() {
+ uint64_t tid;
+ if (pthread_threadid_np(nullptr, &tid) != 0) {
+ return 0;
+ }
+ // Truncate the tid to 32 bits. macOS thread IDs are usually small enough.
+ // And even if we do end up truncating, it doesn't matter much for Jitdump
+ // as long as the process ID is correct.
+ return pid_t(tid);
+}
+# define gettid() gettid_pthread()
+
+const char* get_current_dir_name_cwd() {
+ constexpr size_t CWD_MAX = 256;
+ char* buffer = (char*)malloc(CWD_MAX);
+ if (getcwd(buffer, CWD_MAX) == nullptr) {
+ buffer[0] = 0;
+ }
+ return buffer;
+}
+# define get_current_dir_name() get_current_dir_name_cwd()
+#endif
+
+#include "jit/PerfSpewer.h"
+
+#include <atomic>
+
+#include "jit/Jitdump.h"
+#include "jit/JitSpewer.h"
+#include "jit/LIR.h"
+#include "jit/MIR.h"
+#include "js/JitCodeAPI.h"
+#include "js/Printf.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/MutexIDs.h"
+
+#ifdef XP_WIN
+# include <windef.h>
+# include <codecvt>
+# include <evntprov.h>
+# include <locale>
+# include <string>
+# include <windows.h>
+
+const GUID PROVIDER_JSCRIPT9 = {
+ 0x57277741,
+ 0x3638,
+ 0x4a4b,
+ {0xbd, 0xba, 0x0a, 0xc6, 0xe4, 0x5d, 0xa5, 0x6c}};
+const EVENT_DESCRIPTOR MethodLoad = {0x9, 0x0, 0x0, 0x4, 0xa, 0x1, 0x1};
+
+static REGHANDLE sETWRegistrationHandle = NULL;
+
+static std::atomic<bool> etwCollection = false;
+#endif
+
+using namespace js;
+using namespace js::jit;
+
+enum class PerfModeType { None, Function, Source, IR, IROperands };
+
+static std::atomic<bool> geckoProfiling = false;
+static std::atomic<PerfModeType> PerfMode = PerfModeType::None;
+
+// Mutex to guard access to the profiler vectors and jitdump file if perf
+// profiling is enabled.
+static js::Mutex PerfMutex(mutexid::PerfSpewer);
+
+static PersistentRooted<GCVector<JitCode*, 0, js::SystemAllocPolicy>>
+ jitCodeVector;
+static ProfilerJitCodeVector profilerData;
+
+static bool IsGeckoProfiling() { return geckoProfiling; }
+#ifdef JS_ION_PERF
+static UniqueChars spew_dir;
+static FILE* JitDumpFilePtr = nullptr;
+static void* mmap_address = nullptr;
+static bool IsPerfProfiling() { return JitDumpFilePtr != nullptr; }
+#endif
+
+AutoLockPerfSpewer::AutoLockPerfSpewer() { PerfMutex.lock(); }
+
+AutoLockPerfSpewer::~AutoLockPerfSpewer() { PerfMutex.unlock(); }
+
+#ifdef JS_ION_PERF
+static uint64_t GetMonotonicTimestamp() {
+ struct timespec ts = {};
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000000000 + ts.tv_nsec;
+}
+
+// values are from /usr/include/elf.h
+static uint32_t GetMachineEncoding() {
+# if defined(JS_CODEGEN_X86)
+ return 3; // EM_386
+# elif defined(JS_CODEGEN_X64)
+ return 62; // EM_X86_64
+# elif defined(JS_CODEGEN_ARM)
+ return 40; // EM_ARM
+# elif defined(JS_CODEGEN_ARM64)
+ return 183; // EM_AARCH64
+# elif defined(JS_CODEGEN_MIPS32)
+ return 8; // EM_MIPS
+# elif defined(JS_CODEGEN_MIPS64)
+ return 8; // EM_MIPS
+# else
+ return 0; // Unsupported
+# endif
+}
+
+static void WriteToJitDumpFile(const void* addr, uint32_t size,
+ AutoLockPerfSpewer& lock) {
+ MOZ_RELEASE_ASSERT(JitDumpFilePtr);
+ size_t rv = fwrite(addr, 1, size, JitDumpFilePtr);
+ MOZ_RELEASE_ASSERT(rv == size);
+}
+
+static void WriteJitDumpDebugEntry(uint64_t addr, const char* filename,
+ uint32_t lineno, uint32_t colno,
+ AutoLockPerfSpewer& lock) {
+ JitDumpDebugEntry entry = {addr, lineno, colno};
+ WriteToJitDumpFile(&entry, sizeof(entry), lock);
+ WriteToJitDumpFile(filename, strlen(filename) + 1, lock);
+}
+
+static bool FileExists(const char* filename) {
+ // We don't currently dump external resources to disk.
+ if (strncmp(filename, "http", 4) == 0) {
+ return false;
+ }
+
+ struct stat buf = {};
+ return stat(filename, &buf) == 0;
+}
+
+static void writeJitDumpHeader(AutoLockPerfSpewer& lock) {
+ JitDumpHeader header = {};
+ header.magic = 0x4A695444;
+ header.version = 1;
+ header.total_size = sizeof(header);
+ header.elf_mach = GetMachineEncoding();
+ header.pad1 = 0;
+ header.pid = getpid();
+ header.timestamp = GetMonotonicTimestamp();
+ header.flags = 0;
+
+ WriteToJitDumpFile(&header, sizeof(header), lock);
+}
+
+static bool openJitDump() {
+ if (JitDumpFilePtr) {
+ return true;
+ }
+ AutoLockPerfSpewer lock;
+
+ const ssize_t bufferSize = 256;
+ char filenameBuffer[bufferSize];
+
+ // We want to write absolute filenames into the debug info or else we get the
+ // filenames in the perf report
+ if (getenv("PERF_SPEW_DIR")) {
+ char* env_dir = getenv("PERF_SPEW_DIR");
+ if (env_dir[0] == '/') {
+ spew_dir = JS_smprintf("%s", env_dir);
+ } else {
+ const char* dir = get_current_dir_name();
+ spew_dir = JS_smprintf("%s/%s", dir, env_dir);
+ free((void*)dir);
+ }
+ } else {
+ fprintf(stderr, "Please define PERF_SPEW_DIR as an output directory.\n");
+ return false;
+ }
+
+ if (snprintf(filenameBuffer, bufferSize, "%s/jit-%d.dump", spew_dir.get(),
+ getpid()) >= bufferSize) {
+ return false;
+ }
+
+ MOZ_ASSERT(!JitDumpFilePtr);
+
+ int fd = open(filenameBuffer, O_CREAT | O_TRUNC | O_RDWR, 0666);
+ JitDumpFilePtr = fdopen(fd, "w+");
+
+ if (!JitDumpFilePtr) {
+ return false;
+ }
+
+# ifdef XP_LINUX
+ // We need to mmap the jitdump file for perf to find it.
+ long page_size = sysconf(_SC_PAGESIZE);
+ mmap_address =
+ mmap(nullptr, page_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
+ if (mmap_address == MAP_FAILED) {
+ PerfMode = PerfModeType::None;
+ return false;
+ }
+# endif
+
+ writeJitDumpHeader(lock);
+ return true;
+}
+
+static void CheckPerf() {
+ static bool PerfChecked = false;
+
+ if (!PerfChecked) {
+ const char* env = getenv("IONPERF");
+ if (env == nullptr) {
+ PerfMode = PerfModeType::None;
+ fprintf(stderr,
+ "Warning: JIT perf reporting requires IONPERF set to \"func\" "
+ ", \"src\" or \"ir\". ");
+ fprintf(stderr, "Perf mapping will be deactivated.\n");
+ } else if (!strcmp(env, "src")) {
+ PerfMode = PerfModeType::Source;
+ } else if (!strcmp(env, "ir")) {
+ PerfMode = PerfModeType::IR;
+ } else if (!strcmp(env, "ir-ops")) {
+# ifdef JS_JITSPEW
+ PerfMode = PerfModeType::IROperands;
+# else
+ fprintf(stderr,
+ "Warning: IONPERF=ir-ops requires --enable-jitspew to be "
+ "enabled, defaulting to IONPERF=ir\n");
+ PerfMode = PerfModeType::IR;
+# endif
+ } else if (!strcmp(env, "func")) {
+ PerfMode = PerfModeType::Function;
+ } else {
+ fprintf(stderr, "Use IONPERF=func to record at function granularity\n");
+ fprintf(stderr,
+ "Use IONPERF=ir to record and annotate assembly with IR\n");
+ fprintf(stderr,
+ "Use IONPERF=src to record and annotate assembly with source, if "
+ "available locally\n");
+ exit(0);
+ }
+
+ if (PerfMode != PerfModeType::None) {
+ if (openJitDump()) {
+ PerfChecked = true;
+ return;
+ }
+
+ fprintf(stderr, "Failed to open perf map file. Disabling IONPERF.\n");
+ PerfMode = PerfModeType::None;
+ }
+ PerfChecked = true;
+ }
+}
+#endif
+
+#ifdef XP_WIN
+void NTAPI ETWEnableCallback(LPCGUID aSourceId, ULONG aIsEnabled, UCHAR aLevel,
+ ULONGLONG aMatchAnyKeyword,
+ ULONGLONG aMatchAllKeyword,
+ PEVENT_FILTER_DESCRIPTOR aFilterData,
+ PVOID aCallbackContext) {
+ // This is called on a CRT worker thread. This means this might race with
+ // our main thread, but that is okay.
+ etwCollection = aIsEnabled;
+ PerfMode = aIsEnabled ? PerfModeType::Function : PerfModeType::None;
+}
+
+void RegisterETW() {
+ static bool sHasRegisteredETW = false;
+ if (!sHasRegisteredETW) {
+ if (getenv("ETW_ENABLED")) {
+ EventRegister(&PROVIDER_JSCRIPT9, // GUID that identifies the provider
+ ETWEnableCallback, // Callback for enabling collection
+ NULL, // Context not used
+ &sETWRegistrationHandle // Used when calling EventWrite
+ // and EventUnregister
+ );
+ }
+ sHasRegisteredETW = true;
+ }
+}
+#endif
+
+/* static */
+void PerfSpewer::Init() {
+#ifdef JS_ION_PERF
+ CheckPerf();
+#endif
+#ifdef XP_WIN
+ RegisterETW();
+#endif
+}
+
+static void DisablePerfSpewer(AutoLockPerfSpewer& lock) {
+ fprintf(stderr, "Warning: Disabling PerfSpewer.");
+
+ geckoProfiling = false;
+#ifdef XP_WIN
+ etwCollection = false;
+#endif
+ PerfMode = PerfModeType::None;
+#ifdef JS_ION_PERF
+ long page_size = sysconf(_SC_PAGESIZE);
+ munmap(mmap_address, page_size);
+ fclose(JitDumpFilePtr);
+ JitDumpFilePtr = nullptr;
+#endif
+}
+
+void js::jit::ResetPerfSpewer(bool enabled) {
+ AutoLockPerfSpewer lock;
+
+ profilerData.clear();
+ jitCodeVector.clear();
+ geckoProfiling = enabled;
+}
+
+static JS::JitCodeRecord* CreateProfilerEntry(AutoLockPerfSpewer& lock) {
+ if (!IsGeckoProfiling()) {
+ return nullptr;
+ }
+
+ if (!profilerData.growBy(1)) {
+ DisablePerfSpewer(lock);
+ return nullptr;
+ }
+ return &profilerData.back();
+}
+
+static JS::JitCodeIRInfo* CreateProfilerIREntry(JS::JitCodeRecord* record,
+ AutoLockPerfSpewer& lock) {
+ if (!record) {
+ return nullptr;
+ }
+
+ if (!record->irInfo.growBy(1)) {
+ DisablePerfSpewer(lock);
+ return nullptr;
+ }
+ return &record->irInfo.back();
+}
+
+static JS::JitCodeSourceInfo* CreateProfilerSourceEntry(
+ JS::JitCodeRecord* record, AutoLockPerfSpewer& lock) {
+ if (!record) {
+ return nullptr;
+ }
+
+ if (!record->sourceInfo.growBy(1)) {
+ DisablePerfSpewer(lock);
+ return nullptr;
+ }
+ return &record->sourceInfo.back();
+}
+
+JS::JitOpcodeDictionary::JitOpcodeDictionary() {
+ MOZ_ASSERT(JS_IsInitialized());
+
+#define COPY_JSOP_OPCODE(name, ...) \
+ if (!baselineDictionary.append(JS_smprintf(#name))) { \
+ return; \
+ }
+ FOR_EACH_OPCODE(COPY_JSOP_OPCODE)
+#undef COPY_JSOP_OPCODE
+
+#define COPY_LIR_OPCODE(name, ...) \
+ if (!ionDictionary.append(JS_smprintf(#name))) { \
+ return; \
+ }
+ LIR_OPCODE_LIST(COPY_LIR_OPCODE)
+#undef COPY_LIR_OPCODE
+
+#define COPY_CACHEIR_OPCODE(name, ...) \
+ if (!icDictionary.append(JS_smprintf(#name))) { \
+ return; \
+ }
+ CACHE_IR_OPS(COPY_CACHEIR_OPCODE)
+#undef COPY_CACHEIR_OPCODE
+}
+
+// API to access JitCode data for the Gecko Profiler.
+void JS::JitCodeIterator::getDataForIndex(size_t IteratorIndex) {
+ if (IteratorIndex >= profilerData.length()) {
+ data = nullptr;
+ } else {
+ data = &profilerData[IteratorIndex];
+ }
+}
+
+JS::JitCodeIterator::JitCodeIterator() : iteratorIndex(0) {
+ MOZ_ASSERT(JS_IsInitialized());
+ PerfMutex.lock();
+ getDataForIndex(0);
+}
+
+JS::JitCodeIterator::~JitCodeIterator() { PerfMutex.unlock(); }
+
+static bool PerfSrcEnabled() {
+ return PerfMode == PerfModeType::Source || geckoProfiling;
+}
+
+#ifdef JS_JITSPEW
+static bool PerfIROpsEnabled() { return PerfMode == PerfModeType::IROperands; }
+#endif
+
+static bool PerfIREnabled() {
+ return (PerfMode == PerfModeType::IROperands) ||
+ (PerfMode == PerfModeType::IR) || geckoProfiling;
+}
+
+static bool PerfFuncEnabled() {
+ return PerfMode == PerfModeType::Function || geckoProfiling;
+}
+
+bool js::jit::PerfEnabled() {
+ return PerfSrcEnabled() || PerfIREnabled() || PerfFuncEnabled();
+}
+
+void InlineCachePerfSpewer::recordInstruction(MacroAssembler& masm,
+ CacheOp op) {
+ if (!PerfIREnabled()) {
+ return;
+ }
+ AutoLockPerfSpewer lock;
+
+ if (!opcodes_.emplaceBack(masm.currentOffset(), static_cast<unsigned>(op))) {
+ opcodes_.clear();
+ DisablePerfSpewer(lock);
+ }
+}
+
+#define CHECK_RETURN(x) \
+ if (!(x)) { \
+ AutoLockPerfSpewer lock; \
+ DisablePerfSpewer(lock); \
+ return; \
+ }
+
+void IonPerfSpewer::recordInstruction(MacroAssembler& masm, LInstruction* ins) {
+ if (!PerfIREnabled() && !PerfSrcEnabled()) {
+ return;
+ }
+
+ LNode::Opcode op = ins->op();
+ UniqueChars opcodeStr;
+
+ jsbytecode* bytecodepc = nullptr;
+ if (MDefinition* mir = ins->mirRaw()) {
+ bytecodepc = mir->trackedSite()->pc();
+ }
+
+#ifdef JS_JITSPEW
+ if (PerfIROpsEnabled()) {
+ Sprinter buf;
+ CHECK_RETURN(buf.init());
+ CHECK_RETURN(buf.put(LIRCodeName(op)));
+ ins->printOperands(buf);
+ opcodeStr = buf.release();
+ }
+#endif
+ if (!opcodes_.emplaceBack(masm.currentOffset(), static_cast<unsigned>(op),
+ opcodeStr, bytecodepc)) {
+ opcodes_.clear();
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ }
+}
+
+#ifdef JS_JITSPEW
+static void PrintStackValue(StackValue* stackVal, CompilerFrameInfo& frame,
+ Sprinter& buf) {
+ switch (stackVal->kind()) {
+ /****** Constant ******/
+ case StackValue::Constant: {
+ js::Value constantVal = stackVal->constant();
+ if (constantVal.isInt32()) {
+ CHECK_RETURN(buf.jsprintf("%d", constantVal.toInt32()));
+ } else if (constantVal.isObjectOrNull()) {
+ CHECK_RETURN(buf.jsprintf("obj:%p", constantVal.toObjectOrNull()));
+ } else if (constantVal.isString()) {
+ CHECK_RETURN(buf.put("str:"));
+ CHECK_RETURN(buf.putString(constantVal.toString()));
+ } else if (constantVal.isNumber()) {
+ CHECK_RETURN(buf.jsprintf("num:%f", constantVal.toNumber()));
+ } else if (constantVal.isSymbol()) {
+ CHECK_RETURN(buf.put("sym:"));
+ constantVal.toSymbol()->dump(buf);
+ } else {
+ CHECK_RETURN(buf.jsprintf("raw:%" PRIx64, constantVal.asRawBits()));
+ }
+ } break;
+ /****** Register ******/
+ case StackValue::Register: {
+ Register reg = stackVal->reg().payloadOrValueReg();
+ CHECK_RETURN(buf.put(reg.name()));
+ } break;
+ /****** Stack ******/
+ case StackValue::Stack:
+ CHECK_RETURN(buf.put("stack"));
+ break;
+ /****** ThisSlot ******/
+ case StackValue::ThisSlot: {
+# ifdef JS_HAS_HIDDEN_SP
+ CHECK_RETURN(buf.put("this"));
+# else
+ Address addr = frame.addressOfThis();
+ CHECK_RETURN(buf.jsprintf("this:%s(%d)", addr.base.name(), addr.offset));
+# endif
+ } break;
+ /****** LocalSlot ******/
+ case StackValue::LocalSlot:
+ CHECK_RETURN(buf.jsprintf("local:%u", stackVal->localSlot()));
+ break;
+ /****** ArgSlot ******/
+ case StackValue::ArgSlot:
+ CHECK_RETURN(buf.jsprintf("arg:%u", stackVal->argSlot()));
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected kind");
+ break;
+ }
+}
+#endif
+
+void BaselinePerfSpewer::recordInstruction(JSContext* cx, MacroAssembler& masm,
+ jsbytecode* pc,
+ CompilerFrameInfo& frame) {
+ if (!PerfIREnabled()) {
+ return;
+ }
+
+ JSOp op = JSOp(*pc);
+ UniqueChars opcodeStr;
+
+#ifdef JS_JITSPEW
+ if (PerfIROpsEnabled()) {
+ JSScript* script = frame.script;
+ unsigned numOperands = js::StackUses(op, pc);
+
+ Sprinter buf(cx);
+ CHECK_RETURN(buf.init());
+ CHECK_RETURN(buf.put(js::CodeName(op)));
+
+ switch (op) {
+ case JSOp::SetName:
+ case JSOp::SetGName:
+ case JSOp::BindName:
+ case JSOp::BindGName:
+ case JSOp::GetName:
+ case JSOp::GetGName: {
+ // Emit the name used for these ops
+ Rooted<PropertyName*> name(cx, script->getName(pc));
+ CHECK_RETURN(buf.put(" "));
+ CHECK_RETURN(buf.putString(name));
+ } break;
+ default:
+ break;
+ }
+
+ // Output should be "JSOp (operand1), (operand2), ..."
+ for (unsigned i = 1; i <= numOperands; i++) {
+ CHECK_RETURN(buf.put(" ("));
+ StackValue* stackVal = frame.peek(-int(i));
+ PrintStackValue(stackVal, frame, buf);
+
+ if (i < numOperands) {
+ CHECK_RETURN(buf.put("),"));
+ } else {
+ CHECK_RETURN(buf.put(")"));
+ }
+ }
+ opcodeStr = buf.release();
+ }
+#endif
+
+ if (!opcodes_.emplaceBack(masm.currentOffset(), static_cast<unsigned>(op),
+ opcodeStr)) {
+ opcodes_.clear();
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ }
+}
+
+const char* BaselinePerfSpewer::CodeName(unsigned op) {
+ return js::CodeName(static_cast<JSOp>(op));
+}
+
+const char* BaselineInterpreterPerfSpewer::CodeName(unsigned op) {
+ return js::CodeName(static_cast<JSOp>(op));
+}
+
+const char* IonPerfSpewer::CodeName(unsigned op) {
+ return js::jit::LIRCodeName(static_cast<LNode::Opcode>(op));
+}
+const char* InlineCachePerfSpewer::CodeName(unsigned op) {
+ return js::jit::CacheIRCodeName(static_cast<CacheOp>(op));
+}
+
+void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, JitCode* code,
+ JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+ // Hold the JitCode objects here so they are not GC'd during profiling.
+ if (IsGeckoProfiling()) {
+ if (!jitCodeVector.append(code)) {
+ DisablePerfSpewer(lock);
+ }
+ }
+
+ CollectJitCodeInfo(function_name, reinterpret_cast<void*>(code->raw()),
+ code->instructionsSize(), profilerRecord, lock);
+}
+
+void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, void* code_addr,
+ uint64_t code_size,
+ JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+#ifdef JS_ION_PERF
+ static uint64_t codeIndex = 1;
+
+ if (IsPerfProfiling()) {
+ JitDumpLoadRecord record = {};
+
+ record.header.id = JIT_CODE_LOAD;
+ record.header.total_size =
+ sizeof(record) + strlen(function_name.get()) + 1 + code_size;
+ record.header.timestamp = GetMonotonicTimestamp();
+ record.pid = getpid();
+ record.tid = gettid();
+ record.vma = uint64_t(code_addr);
+ record.code_addr = uint64_t(code_addr);
+ record.code_size = code_size;
+ record.code_index = codeIndex++;
+
+ WriteToJitDumpFile(&record, sizeof(record), lock);
+ WriteToJitDumpFile(function_name.get(), strlen(function_name.get()) + 1,
+ lock);
+ WriteToJitDumpFile(code_addr, code_size, lock);
+ }
+#endif
+#ifdef XP_WIN
+ if (etwCollection) {
+ void* scriptContextId = NULL;
+ uint32_t flags = 0;
+ uint64_t map = 0;
+ uint64_t assembly = 0;
+ uint32_t line_col = 0;
+ uint32_t method = 0;
+
+ int name_len = strlen(function_name.get());
+ std::wstring name(name_len + 1, '\0');
+ if (MultiByteToWideChar(CP_UTF8, 0, function_name.get(), name_len,
+ name.data(), name.size()) == 0) {
+ DisablePerfSpewer(lock);
+ return;
+ }
+
+ EVENT_DATA_DESCRIPTOR EventData[10];
+
+ EventDataDescCreate(&EventData[0], &scriptContextId, sizeof(PVOID));
+ EventDataDescCreate(&EventData[1], &code_addr, sizeof(PVOID));
+ EventDataDescCreate(&EventData[2], &code_size, sizeof(unsigned __int64));
+ EventDataDescCreate(&EventData[3], &method, sizeof(uint32_t));
+ EventDataDescCreate(&EventData[4], &flags, sizeof(const unsigned short));
+ EventDataDescCreate(&EventData[5], &map, sizeof(const unsigned short));
+ EventDataDescCreate(&EventData[6], &assembly, sizeof(unsigned __int64));
+ EventDataDescCreate(&EventData[7], &line_col, sizeof(const unsigned int));
+ EventDataDescCreate(&EventData[8], &line_col, sizeof(const unsigned int));
+ EventDataDescCreate(&EventData[9], name.c_str(),
+ sizeof(wchar_t) * (name.length() + 1));
+
+ ULONG result = EventWrite(
+ sETWRegistrationHandle, // From EventRegister
+ &MethodLoad, // EVENT_DESCRIPTOR generated from the manifest
+ (ULONG)10, // Size of the array of EVENT_DATA_DESCRIPTORs
+ EventData // Array of descriptors that contain the event data
+ );
+
+ if (result != ERROR_SUCCESS) {
+ DisablePerfSpewer(lock);
+ return;
+ }
+ }
+#endif
+
+ if (IsGeckoProfiling()) {
+ profilerRecord->instructionSize = code_size;
+ profilerRecord->code_addr = uint64_t(code_addr);
+ profilerRecord->functionName = std::move(function_name);
+ }
+}
+
+void PerfSpewer::recordOffset(MacroAssembler& masm, const char* msg) {
+ if (!PerfIREnabled()) {
+ return;
+ }
+
+ UniqueChars offsetStr = DuplicateString(msg);
+ if (!opcodes_.emplaceBack(masm.currentOffset(), offsetStr)) {
+ opcodes_.clear();
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ }
+}
+
+void PerfSpewer::saveJitCodeIRInfo(JitCode* code,
+ JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+#ifdef JS_ION_PERF
+ static uint32_t filenameCounter = 0;
+ UniqueChars scriptFilename;
+ FILE* scriptFile = nullptr;
+
+ if (IsPerfProfiling()) {
+ scriptFilename = JS_smprintf("%s/jitdump-script-%u.%u.txt", spew_dir.get(),
+ filenameCounter++, getpid());
+ scriptFile = fopen(scriptFilename.get(), "w");
+ if (!scriptFile) {
+ DisablePerfSpewer(lock);
+ return;
+ }
+
+ JitDumpDebugRecord debug_record = {};
+ uint64_t n_records = opcodes_.length();
+
+ debug_record.header.id = JIT_CODE_DEBUG_INFO;
+ debug_record.header.total_size =
+ sizeof(debug_record) + n_records * (sizeof(JitDumpDebugEntry) +
+ strlen(scriptFilename.get()) + 1);
+ debug_record.header.timestamp = GetMonotonicTimestamp();
+ debug_record.code_addr = uint64_t(code->raw());
+ debug_record.nr_entry = n_records;
+
+ WriteToJitDumpFile(&debug_record, sizeof(debug_record), lock);
+ }
+#endif
+
+ if (profilerRecord) {
+ profilerRecord->tier = GetTier();
+ }
+
+ for (size_t i = 0; i < opcodes_.length(); i++) {
+ OpcodeEntry& entry = opcodes_[i];
+#ifdef JS_ION_PERF
+ if (IsPerfProfiling()) {
+ // If a string was recorded for this offset, use that instead.
+ if (entry.str) {
+ fprintf(scriptFile, "%s\n", entry.str.get());
+ } else {
+ fprintf(scriptFile, "%s\n", CodeName(entry.opcode));
+ }
+ uint64_t addr = uint64_t(code->raw()) + entry.offset;
+ uint64_t lineno = i + 1;
+ WriteJitDumpDebugEntry(addr, scriptFilename.get(), lineno, 0, lock);
+ }
+#endif
+
+ if (JS::JitCodeIRInfo* irInfo =
+ CreateProfilerIREntry(profilerRecord, lock)) {
+ irInfo->offset = entry.offset;
+ irInfo->opcode = entry.opcode;
+ // Profiler API now owns this string, if defined.
+ irInfo->str = std::move(entry.str);
+ }
+ }
+ opcodes_.clear();
+
+#ifdef JS_ION_PERF
+ if (IsPerfProfiling()) {
+ fclose(scriptFile);
+ }
+#endif
+}
+
+void BaselinePerfSpewer::saveJitCodeSourceInfo(
+ JSScript* script, JitCode* code, JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+ const char* filename = script->filename();
+ if (!filename) {
+ return;
+ }
+
+#ifdef JS_ION_PERF
+ bool perfProfiling = IsPerfProfiling() && FileExists(filename);
+
+ // If we are using perf, we need to know the number of debug entries ahead of
+ // time for the header.
+ if (perfProfiling) {
+ JitDumpDebugRecord debug_record = {};
+ uint64_t n_records = 0;
+
+ for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+ const auto* const sn = *iter;
+ switch (sn->type()) {
+ case SrcNoteType::SetLine:
+ case SrcNoteType::NewLine:
+ case SrcNoteType::ColSpan:
+ if (sn->delta() > 0) {
+ n_records++;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Nothing to do
+ if (n_records == 0) {
+ return;
+ }
+
+ debug_record.header.id = JIT_CODE_DEBUG_INFO;
+ debug_record.header.total_size =
+ sizeof(debug_record) +
+ n_records * (sizeof(JitDumpDebugEntry) + strlen(filename) + 1);
+
+ debug_record.header.timestamp = GetMonotonicTimestamp();
+ debug_record.code_addr = uint64_t(code->raw());
+ debug_record.nr_entry = n_records;
+
+ WriteToJitDumpFile(&debug_record, sizeof(debug_record), lock);
+ }
+#endif
+
+ uint32_t lineno = script->lineno();
+ uint32_t colno = script->column();
+ uint64_t offset = 0;
+ for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) {
+ const auto* sn = *iter;
+ offset += sn->delta();
+
+ SrcNoteType type = sn->type();
+ if (type == SrcNoteType::SetLine) {
+ lineno = SrcNote::SetLine::getLine(sn, script->lineno());
+ colno = 0;
+ } else if (type == SrcNoteType::NewLine) {
+ lineno++;
+ colno = 0;
+ } else if (type == SrcNoteType::ColSpan) {
+ colno += SrcNote::ColSpan::getSpan(sn);
+ } else {
+ continue;
+ }
+
+ // Don't add entries that won't change the offset
+ if (sn->delta() <= 0) {
+ continue;
+ }
+
+ if (JS::JitCodeSourceInfo* srcInfo =
+ CreateProfilerSourceEntry(profilerRecord, lock)) {
+ srcInfo->offset = offset;
+ srcInfo->lineno = lineno;
+ srcInfo->colno = colno;
+ srcInfo->filename = JS_smprintf("%s", filename);
+ }
+
+#ifdef JS_ION_PERF
+ if (perfProfiling) {
+ WriteJitDumpDebugEntry(uint64_t(code->raw()) + offset, filename, lineno,
+ colno, lock);
+ }
+#endif
+ }
+}
+
+void IonPerfSpewer::saveJitCodeSourceInfo(JSScript* script, JitCode* code,
+ JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+ const char* filename = script->filename();
+ if (!filename) {
+ return;
+ }
+
+#ifdef JS_ION_PERF
+ bool perfProfiling = IsPerfProfiling() && FileExists(filename);
+
+ if (perfProfiling) {
+ JitDumpDebugRecord debug_record = {};
+
+ uint64_t n_records = 0;
+ for (OpcodeEntry& entry : opcodes_) {
+ if (entry.bytecodepc) {
+ n_records++;
+ }
+ }
+
+ debug_record.header.id = JIT_CODE_DEBUG_INFO;
+ debug_record.header.total_size =
+ sizeof(debug_record) +
+ n_records * (sizeof(JitDumpDebugEntry) + strlen(filename) + 1);
+ debug_record.header.timestamp = GetMonotonicTimestamp();
+ debug_record.code_addr = uint64_t(code->raw());
+ debug_record.nr_entry = n_records;
+
+ WriteToJitDumpFile(&debug_record, sizeof(debug_record), lock);
+ }
+#endif
+ uint32_t lineno = 0;
+ uint32_t colno = 0;
+
+ for (OpcodeEntry& entry : opcodes_) {
+ jsbytecode* pc = entry.bytecodepc;
+ if (!pc) {
+ continue;
+ }
+ // We could probably make this a bit faster by caching the previous pc
+ // offset, but it currently doesn't seem noticeable when testing.
+ lineno = PCToLineNumber(script, pc, &colno);
+
+ if (JS::JitCodeSourceInfo* srcInfo =
+ CreateProfilerSourceEntry(profilerRecord, lock)) {
+ srcInfo->offset = entry.offset;
+ srcInfo->lineno = lineno;
+ srcInfo->colno = colno;
+ srcInfo->filename = JS_smprintf("%s", filename);
+ }
+
+#ifdef JS_ION_PERF
+ if (perfProfiling) {
+ WriteJitDumpDebugEntry(uint64_t(code->raw()) + entry.offset, filename,
+ lineno, colno, lock);
+ }
+#endif
+ }
+}
+
+static UniqueChars GetFunctionDesc(const char* tierName, JSContext* cx,
+ JSScript* script,
+ const char* stubName = nullptr) {
+ MOZ_ASSERT(script && tierName && cx);
+ UniqueChars funName;
+ if (script->function() && script->function()->displayAtom()) {
+ funName = AtomToPrintableString(cx, script->function()->displayAtom());
+ }
+
+ if (stubName) {
+ return JS_smprintf("%s: %s : %s (%s:%u:%u)", tierName, stubName,
+ funName ? funName.get() : "*", script->filename(),
+ script->lineno(), script->column());
+ }
+ return JS_smprintf("%s: %s (%s:%u:%u)", tierName,
+ funName ? funName.get() : "*", script->filename(),
+ script->lineno(), script->column());
+}
+
+void PerfSpewer::saveDebugInfo(JSScript* script, JitCode* code,
+ JS::JitCodeRecord* profilerRecord,
+ AutoLockPerfSpewer& lock) {
+ MOZ_ASSERT(code);
+ if (PerfIREnabled()) {
+ saveJitCodeIRInfo(code, profilerRecord, lock);
+ } else if (PerfSrcEnabled() && script) {
+ saveJitCodeSourceInfo(script, code, profilerRecord, lock);
+ }
+}
+
+void PerfSpewer::saveProfile(JitCode* code, UniqueChars& desc,
+ JSScript* script) {
+ MOZ_ASSERT(PerfEnabled());
+ MOZ_ASSERT(code && desc);
+ AutoLockPerfSpewer lock;
+ JS::JitCodeRecord* profilerRecord = CreateProfilerEntry(lock);
+
+ saveDebugInfo(script, code, profilerRecord, lock);
+ CollectJitCodeInfo(desc, code, profilerRecord, lock);
+}
+
+void IonICPerfSpewer::saveProfile(JSContext* cx, JSScript* script,
+ JitCode* code, const char* stubName) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = GetFunctionDesc("IonIC", cx, script, stubName);
+ PerfSpewer::saveProfile(code, desc, nullptr);
+}
+
+void BaselineICPerfSpewer::saveProfile(JitCode* code, const char* stubName) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = JS_smprintf("BaselineIC: %s", stubName);
+ PerfSpewer::saveProfile(code, desc, nullptr);
+}
+
+void BaselinePerfSpewer::saveProfile(JSContext* cx, JSScript* script,
+ JitCode* code) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = GetFunctionDesc("Baseline", cx, script);
+ PerfSpewer::saveProfile(code, desc, script);
+}
+
+void BaselineInterpreterPerfSpewer::saveProfile(JitCode* code) {
+ if (!PerfEnabled()) {
+ return;
+ }
+
+ enum class SpewKind { Uninitialized, SingleSym, MultiSym };
+
+ // Check which type of Baseline Interpreter Spew is requested.
+ static SpewKind kind = SpewKind::Uninitialized;
+ if (kind == SpewKind::Uninitialized) {
+ if (getenv("IONPERF_SINGLE_BLINTERP")) {
+ kind = SpewKind::SingleSym;
+ } else {
+ kind = SpewKind::MultiSym;
+ }
+ }
+
+ // For SingleSym, just emit one "BaselineInterpreter" symbol
+ // and emit the opcodes as IR if IONPERF=ir is used.
+ if (kind == SpewKind::SingleSym) {
+ UniqueChars desc = DuplicateString("BaselineInterpreter");
+ PerfSpewer::saveProfile(code, desc, nullptr);
+ return;
+ }
+
+ // For MultiSym, split up each opcode into its own symbol.
+ // No IR is emitted in this case, so we can skip PerfSpewer::saveProfile.
+ MOZ_ASSERT(kind == SpewKind::MultiSym);
+ for (size_t i = 1; i < opcodes_.length(); i++) {
+ uintptr_t base = uintptr_t(code->raw()) + opcodes_[i - 1].offset;
+ uintptr_t size = opcodes_[i].offset - opcodes_[i - 1].offset;
+
+ UniqueChars rangeName;
+ if (opcodes_[i - 1].str) {
+ rangeName = JS_smprintf("BlinterpOp: %s", opcodes_[i - 1].str.get());
+ } else {
+ rangeName =
+ JS_smprintf("BlinterpOp: %s", CodeName(opcodes_[i - 1].opcode));
+ }
+
+ // If rangeName is empty, we probably went OOM.
+ if (!rangeName) {
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ return;
+ }
+
+ MOZ_ASSERT(base + size <=
+ uintptr_t(code->raw()) + code->instructionsSize());
+ CollectPerfSpewerJitCodeProfile(base, size, rangeName.get());
+ }
+}
+
+void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm,
+ JSOp op) {
+ if (!PerfEnabled()) {
+ return;
+ }
+
+ if (!opcodes_.emplaceBack(masm.currentOffset(), unsigned(op))) {
+ opcodes_.clear();
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ }
+}
+
+void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm,
+ const char* name) {
+ if (!PerfEnabled()) {
+ return;
+ }
+
+ UniqueChars desc = DuplicateString(name);
+ if (!opcodes_.emplaceBack(masm.currentOffset(), desc)) {
+ opcodes_.clear();
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ }
+}
+
+void IonPerfSpewer::saveProfile(JSContext* cx, JSScript* script,
+ JitCode* code) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = GetFunctionDesc("Ion", cx, script);
+ PerfSpewer::saveProfile(code, desc, script);
+}
+
+void js::jit::CollectPerfSpewerJitCodeProfile(JitCode* code, const char* msg) {
+ if (!code || !PerfEnabled()) {
+ return;
+ }
+
+ size_t size = code->instructionsSize();
+ if (size > 0) {
+ AutoLockPerfSpewer lock;
+
+ JS::JitCodeRecord* profilerRecord = CreateProfilerEntry(lock);
+ UniqueChars desc = JS_smprintf("%s", msg);
+ PerfSpewer::CollectJitCodeInfo(desc, code, profilerRecord, lock);
+ }
+}
+
+void js::jit::CollectPerfSpewerJitCodeProfile(uintptr_t base, uint64_t size,
+ const char* msg) {
+ if (!PerfEnabled()) {
+ return;
+ }
+
+ if (size > 0) {
+ AutoLockPerfSpewer lock;
+
+ JS::JitCodeRecord* profilerRecord = CreateProfilerEntry(lock);
+ UniqueChars desc = JS_smprintf("%s", msg);
+ PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base), size,
+ profilerRecord, lock);
+ }
+}
+
+void js::jit::CollectPerfSpewerWasmMap(uintptr_t base, uintptr_t size,
+ const char* filename,
+ const char* annotation) {
+ if (size == 0U || !PerfEnabled()) {
+ return;
+ }
+ AutoLockPerfSpewer lock;
+
+ JS::JitCodeRecord* profilerRecord = CreateProfilerEntry(lock);
+ UniqueChars desc = JS_smprintf("%s: Function %s", filename, annotation);
+ PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base),
+ uint64_t(size), profilerRecord, lock);
+}
+
+void js::jit::CollectPerfSpewerWasmFunctionMap(uintptr_t base, uintptr_t size,
+ const char* filename,
+ unsigned lineno,
+ const char* funcName) {
+ if (size == 0U || !PerfEnabled()) {
+ return;
+ }
+ AutoLockPerfSpewer lock;
+
+ JS::JitCodeRecord* profilerRecord = CreateProfilerEntry(lock);
+ UniqueChars desc =
+ JS_smprintf("%s:%u: Function %s", filename, lineno, funcName);
+ PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast<void*>(base),
+ uint64_t(size), profilerRecord, lock);
+}
+
+void js::jit::PerfSpewerRangeRecorder::appendEntry(UniqueChars& desc) {
+ if (!ranges.append(std::make_pair(masm.currentOffset(), std::move(desc)))) {
+ AutoLockPerfSpewer lock;
+ DisablePerfSpewer(lock);
+ ranges.clear();
+ }
+}
+
+void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = DuplicateString(name);
+ appendEntry(desc);
+}
+
+void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name,
+ JSContext* cx,
+ JSScript* script) {
+ if (!PerfEnabled()) {
+ return;
+ }
+ UniqueChars desc = GetFunctionDesc(name, cx, script);
+ appendEntry(desc);
+}
+
+void js::jit::PerfSpewerRangeRecorder::collectRangesForJitCode(JitCode* code) {
+ if (!PerfEnabled() || ranges.empty()) {
+ return;
+ }
+
+ uintptr_t basePtr = uintptr_t(code->raw());
+ uintptr_t offsetStart = 0;
+
+ for (OffsetPair& pair : ranges) {
+ uint32_t offsetEnd = std::get<0>(pair);
+ uintptr_t rangeSize = uintptr_t(offsetEnd - offsetStart);
+ const char* rangeName = std::get<1>(pair).get();
+
+ CollectPerfSpewerJitCodeProfile(basePtr + offsetStart, rangeSize,
+ rangeName);
+ offsetStart = offsetEnd;
+ }
+
+ MOZ_ASSERT(offsetStart <= code->instructionsSize());
+ ranges.clear();
+}