/* -*- 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 # include # include # include #endif #if defined(JS_ION_PERF) && defined(XP_LINUX) && !defined(ANDROID) && \ defined(__GLIBC__) # include # include # include # include # define gettid() static_cast(syscall(__NR_gettid)) #endif #if defined(JS_ION_PERF) && defined(XP_MACOSX) # include # include 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 #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 # include # include # include # include # include 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 etwCollection = false; #endif using namespace js; using namespace js::jit; enum class PerfModeType { None, Function, Source, IR, IROperands }; static std::atomic geckoProfiling = false; static std::atomic 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> 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(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(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 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(op), opcodeStr)) { opcodes_.clear(); AutoLockPerfSpewer lock; DisablePerfSpewer(lock); } } const char* BaselinePerfSpewer::CodeName(unsigned op) { return js::CodeName(static_cast(op)); } const char* BaselineInterpreterPerfSpewer::CodeName(unsigned op) { return js::CodeName(static_cast(op)); } const char* IonPerfSpewer::CodeName(unsigned op) { return js::jit::LIRCodeName(static_cast(op)); } const char* InlineCachePerfSpewer::CodeName(unsigned op) { return js::jit::CacheIRCodeName(static_cast(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(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(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(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(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(); }