/* -*- 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" #include "js/Utility.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(ANDROID) || defined(XP_DARWIN)) # include # include # include char* get_current_dir_name() { char* buffer = js_pod_malloc(PATH_MAX); if (buffer == nullptr) { return nullptr; } if (getcwd(buffer, PATH_MAX) == nullptr) { js_free(buffer); return nullptr; } return buffer; } #endif #if defined(JS_ION_PERF) && defined(XP_DARWIN) # 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() #endif #include "jit/PerfSpewer.h" #include #include "jit/BaselineFrameInfo.h" #include "jit/CacheIR.h" #include "jit/Jitdump.h" #include "jit/JitSpewer.h" #include "jit/LIR.h" #include "jit/MIR-wasm.h" #include "jit/MIR.h" #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset #include "js/Exception.h" #include "js/JitCodeAPI.h" #include "js/Printf.h" #include "vm/BytecodeUtil.h" #include "vm/MutexIDs.h" #ifdef XP_WIN # include "util/WindowsWrapper.h" # 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, IRGraph }; 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. MOZ_RUNINIT static js::Mutex PerfMutex(mutexid::PerfSpewer); MOZ_RUNINIT static PersistentRooted< GCVector> jitCodeVector; MOZ_RUNINIT static ProfilerJitCodeVector profilerData; static bool IsGeckoProfiling() { return geckoProfiling; } #ifdef JS_ION_PERF constinit static UniqueChars spew_dir; static FILE* JitDumpFilePtr = nullptr; static void* mmap_address = nullptr; static char* jitDumpBuffer = nullptr; static bool IsPerfProfiling() { return JitDumpFilePtr != nullptr; } #endif AutoLockPerfSpewer::AutoLockPerfSpewer() { PerfMutex.lock(); } AutoLockPerfSpewer::~AutoLockPerfSpewer() { PerfMutex.unlock(); } #ifdef JS_ION_PERF static uint64_t GetMonotonicTimestamp() { using mozilla::TimeStamp; # ifdef XP_LINUX return TimeStamp::Now().RawClockMonotonicNanosecondsSinceBoot(); # elif XP_WIN return TimeStamp::Now().RawQueryPerformanceCounterValue(); # elif XP_DARWIN return TimeStamp::Now().RawMachAbsoluteTimeNanoseconds(); # else MOZ_CRASH("no timestamp"); # endif } // 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_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 discrim, AutoLockPerfSpewer& lock) { JitDumpDebugEntry entry = {addr, lineno, discrim}; WriteToJitDumpFile(&entry, sizeof(entry), lock); WriteToJitDumpFile(filename, strlen(filename) + 1, lock); } 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(); if (!dir) { fprintf(stderr, "couldn't get current dir name\n"); return false; } spew_dir = JS_smprintf("%s/%s", dir, env_dir); js_free((void*)dir); } } else { fprintf(stderr, "Please define PERF_SPEW_DIR as an output directory.\n"); return false; } if (SprintfBuf(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; } // Allocate a large buffer to reduce write() syscall overhead. constexpr size_t kJitDumpBufferSize = 2 * 1024 * 1024; jitDumpBuffer = js_pod_malloc(kJitDumpBufferSize); if (!jitDumpBuffer) { fclose(JitDumpFilePtr); JitDumpFilePtr = nullptr; return false; } setvbuf(JitDumpFilePtr, jitDumpBuffer, _IOFBF, kJitDumpBufferSize); # ifdef XP_LINUX // We need to mmap the jitdump file for perf to find it. long page_size = sysconf(_SC_PAGESIZE); int prot = PROT_READ | PROT_EXEC; // The mmap call fails on some Android devices if PROT_EXEC is specified, and // it does not appear to be required by simpleperf, so omit it on Android. # ifdef ANDROID prot &= ~PROT_EXEC; # endif mmap_address = mmap(nullptr, page_size, prot, 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; } 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, "ir-graph")) { # ifdef JS_JITSPEW PerfMode = PerfModeType::IRGraph; # else fprintf(stderr, "Warning: IONPERF=ir-graph 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"); # ifdef JS_JITSPEW fprintf(stderr, "Use IONPERF=ir-ops to record and annotate assembly with IR that " "shows operands\n"); fprintf(stderr, "Use IONPERF=ir-graph to record structured IR graphs for " "visualization\n"); # endif 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 ResetPerfSpewer(AutoLockPerfSpewer& lock, bool enabled) { profilerData.clear(); jitCodeVector.clear(); geckoProfiling = enabled; } void js::jit::ResetPerfSpewer(bool enabled) { AutoLockPerfSpewer lock; ::ResetPerfSpewer(lock, enabled); } static void DisablePerfSpewer(AutoLockPerfSpewer& lock) { fprintf(stderr, "Warning: Disabling PerfSpewer.\n"); #ifdef XP_WIN etwCollection = false; #endif ResetPerfSpewer(lock, false); if (PerfMode == PerfModeType::None) { return; } PerfMode = PerfModeType::None; #ifdef JS_ION_PERF long page_size = sysconf(_SC_PAGESIZE); munmap(mmap_address, page_size); fclose(JitDumpFilePtr); JitDumpFilePtr = nullptr; js_free(jitDumpBuffer); jitDumpBuffer = nullptr; #endif } static void DisablePerfSpewer() { AutoLockPerfSpewer lock; DisablePerfSpewer(lock); } static bool MaybeCreateProfilerEntry(AutoLockPerfSpewer& lock, JS::JitCodeRecord*& outProfilerRecord) { outProfilerRecord = nullptr; if (!IsGeckoProfiling()) { return true; } if (!profilerData.growBy(1)) { DisablePerfSpewer(lock); return false; } outProfilerRecord = &profilerData.back(); return true; } static JS::JitCodeSourceInfo* CreateProfilerSourceEntry( JS::JitCodeRecord* record, AutoLockPerfSpewer& lock) { MOZ_ASSERT(record); if (!record->sourceInfo.growBy(1)) { DisablePerfSpewer(lock); return nullptr; } return &record->sourceInfo.back(); } // API to lookup JitCode data for the Gecko Profiler. JS::JitCodeRecord* JS::LookupJitCodeRecord(uint64_t addr) { if (!JS_IsInitialized()) { return nullptr; } AutoLockPerfSpewer lock; // Search through profilerData for a record that contains this address for (auto& record : profilerData) { if (addr >= record.code_addr && addr < record.code_addr + record.instructionSize) { return &record; } } return nullptr; } static bool PerfSrcEnabled() { return PerfMode == PerfModeType::Source || IsGeckoProfiling(); } #ifdef JS_JITSPEW static bool PerfIROpsEnabled() { return PerfMode == PerfModeType::IROperands; } static bool PerfIRGraphEnabled() { return PerfMode == PerfModeType::IRGraph; } #endif static bool PerfIREnabled() { return (PerfMode == PerfModeType::IRGraph) || (PerfMode == PerfModeType::IROperands) || (PerfMode == PerfModeType::IR); } bool js::jit::PerfEnabled() { return PerfMode != PerfModeType::None || IsGeckoProfiling(); } void InlineCachePerfSpewer::recordInstruction(MacroAssembler& masm, const CacheOp& op) { if (!PerfIREnabled()) { return; } AutoLockPerfSpewer lock; recordOpcode(masm.currentOffset() - startOffset_, static_cast(op)); } #define CHECK_RETURN(x) \ if (!(x)) { \ disable(); \ return; \ } void IonPerfSpewer::disable() { #ifdef JS_JITSPEW if (graphSpewer_) { graphPrinter_.finish(); graphSpewer_ = nullptr; } #endif PerfSpewer::disable(); } void IonPerfSpewer::startRecording(const wasm::CodeMetadata* wasmCodeMeta) { PerfSpewer::startRecording(); #ifdef JS_JITSPEW if (PerfIRGraphEnabled()) { graphPrinter_.init(irFile_); graphSpewer_ = MakeUnique(graphPrinter_, wasmCodeMeta); if (!graphSpewer_) { disable(); } graphSpewer_->begin(); graphSpewer_->beginAnonFunction(); } #endif } void IonPerfSpewer::endRecording() { #ifdef JS_JITSPEW if (graphSpewer_) { graphSpewer_->endFunction(); graphSpewer_->end(); graphPrinter_.finish(); graphSpewer_ = nullptr; } #endif PerfSpewer::endRecording(); } void IonPerfSpewer::recordPass(const char* pass, MIRGraph* graph, BacktrackingAllocator* ra) { #ifdef JS_JITSPEW if (PerfIRGraphEnabled() && graphSpewer_) { graphSpewer_->spewPass(pass, graph, ra); } #endif } void IonPerfSpewer::recordInstruction(MacroAssembler& masm, LInstruction* ins) { uint32_t offset = masm.currentOffset() - startOffset_; if (PerfSrcEnabled()) { uint32_t line = 0; uint32_t column = 0; if (MDefinition* mir = ins->mirRaw()) { jsbytecode* pc = mir->trackedSite()->pc(); JSScript* script = mir->trackedSite()->script(); JS::LimitedColumnNumberOneOrigin colno; line = PCToLineNumber(script, pc, &colno); column = colno.oneOriginValue(); } if (!debugInfo_.emplaceBack(offset, line, column)) { disable(); } return; } if (!PerfIREnabled()) { return; } #ifdef JS_JITSPEW if (PerfIRGraphEnabled()) { if (!debugInfo_.emplaceBack(offset, ins->id(), 0)) { disable(); } return; } #endif LNode::Opcode op = ins->op(); UniqueChars opcodeStr; #ifdef JS_JITSPEW if (PerfIROpsEnabled()) { Sprinter buf; CHECK_RETURN(buf.init()); buf.put(LIRCodeName(op)); ins->printOperands(buf); opcodeStr = buf.release(); } #endif recordOpcode(offset, static_cast(op), std::move(opcodeStr)); } #ifdef JS_JITSPEW static void PrintStackValue(JSContext* maybeCx, StackValue* stackVal, CompilerFrameInfo& frame, Sprinter& buf) { switch (stackVal->kind()) { /****** Constant ******/ case StackValue::Constant: { js::Value constantVal = stackVal->constant(); if (constantVal.isInt32()) { buf.printf("%d", constantVal.toInt32()); } else if (constantVal.isObjectOrNull()) { buf.printf("obj:%p", constantVal.toObjectOrNull()); } else if (constantVal.isString()) { if (maybeCx) { buf.put("str:"); buf.putString(maybeCx, constantVal.toString()); } else { buf.put("str"); } } else if (constantVal.isNumber()) { buf.printf("num:%f", constantVal.toNumber()); } else if (constantVal.isSymbol()) { if (maybeCx) { buf.put("sym:"); constantVal.toSymbol()->dump(buf); } else { buf.put("sym"); } } else { buf.printf("raw:%" PRIx64, constantVal.asRawBits()); } } break; /****** Register ******/ case StackValue::Register: { Register reg = stackVal->reg().payloadOrValueReg(); buf.put(reg.name()); } break; /****** Stack ******/ case StackValue::Stack: buf.put("stack"); break; /****** ThisSlot ******/ case StackValue::ThisSlot: { # ifdef JS_HAS_HIDDEN_SP buf.put("this"); # else Address addr = frame.addressOfThis(); buf.printf("this:%s(%d)", addr.base.name(), addr.offset); # endif } break; /****** LocalSlot ******/ case StackValue::LocalSlot: buf.printf("local:%u", stackVal->localSlot()); break; /****** ArgSlot ******/ case StackValue::ArgSlot: buf.printf("arg:%u", stackVal->argSlot()); break; default: MOZ_CRASH("Unexpected kind"); break; } } #endif [[nodiscard]] bool WasmBaselinePerfSpewer::needsToRecordInstruction() const { return PerfIREnabled() || PerfSrcEnabled(); } void WasmBaselinePerfSpewer::recordInstruction(MacroAssembler& masm, const wasm::OpBytes& op) { MOZ_ASSERT(needsToRecordInstruction()); if (!op.canBePacked()) { return; } recordOpcode(masm.currentOffset() - startOffset_, op.toPacked()); } void BaselinePerfSpewer::recordInstruction( MacroAssembler& masm, jsbytecode* pc, unsigned line, JS::LimitedColumnNumberOneOrigin column, CompilerFrameInfo& frame) { uint32_t offset = masm.currentOffset() - startOffset_; if (PerfSrcEnabled()) { if (!debugInfo_.emplaceBack(offset, line, column.oneOriginValue())) { disable(); } return; } if (!PerfIREnabled()) { return; } JSOp op = JSOp(*pc); UniqueChars opcodeStr; #ifdef JS_JITSPEW if (PerfIROpsEnabled()) { JSScript* script = frame.script; unsigned numOperands = js::StackUses(op, pc); JSContext* maybeCx = TlsContext.get(); Sprinter buf(maybeCx); CHECK_RETURN(buf.init()); buf.put(js::CodeName(op)); if (maybeCx) { switch (op) { case JSOp::SetName: case JSOp::SetGName: case JSOp::BindName: case JSOp::BindUnqualifiedName: case JSOp::BindUnqualifiedGName: case JSOp::GetName: case JSOp::GetGName: { // Emit the name used for these ops Rooted name(maybeCx, script->getName(pc)); buf.put(" "); buf.putString(maybeCx, name); } break; default: break; } // Output should be "JSOp (operand1), (operand2), ..." for (unsigned i = 1; i <= numOperands; i++) { buf.put(" ("); StackValue* stackVal = frame.peek(-int(i)); PrintStackValue(maybeCx, stackVal, frame, buf); if (i < numOperands) { buf.put("),"); } else { buf.put(")"); } } } opcodeStr = buf.release(); } #endif recordOpcode(offset, static_cast(op), std::move(opcodeStr)); } const char* BaselinePerfSpewer::CodeName(uint32_t op) { return js::CodeName(static_cast(op)); } const char* BaselineInterpreterPerfSpewer::CodeName(uint32_t op) { return js::CodeName(static_cast(op)); } const char* IonPerfSpewer::CodeName(uint32_t op) { return js::jit::LIRCodeName(static_cast(op)); } const char* IonPerfSpewer::IRFileExtension() { #ifdef JS_JITSPEW if (PerfIRGraphEnabled()) { return ".iongraph.json"; } #endif return ".txt"; } const char* WasmBaselinePerfSpewer::CodeName(uint32_t op) { return wasm::OpBytes::fromPacked(op).toString(); } const char* InlineCachePerfSpewer::CodeName(uint32_t op) { return js::jit::CacheIRCodeName(static_cast(op)); } void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, JitCode* code, JS::JitCodeRecord* maybeProfilerRecord, AutoLockPerfSpewer& lock) { // Hold the JitCode objects here so they are not GC'd during profiling. if (IsGeckoProfiling()) { if (!jitCodeVector.append(code)) { DisablePerfSpewer(lock); return; } } CollectJitCodeInfo(function_name, reinterpret_cast(code->raw()), code->instructionsSize(), maybeProfilerRecord, lock); } void PerfSpewer::CollectJitCodeInfo(UniqueChars& function_name, void* code_addr, uint64_t code_size, JS::JitCodeRecord* maybeProfilerRecord, 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()) { MOZ_ASSERT(maybeProfilerRecord); maybeProfilerRecord->instructionSize = code_size; maybeProfilerRecord->code_addr = uint64_t(code_addr); } } void PerfSpewer::recordOffset(MacroAssembler& masm, const char* msg) { if (!PerfIREnabled()) { return; } #ifdef JS_JITSPEW if (PerfIRGraphEnabled()) { return; } #endif UniqueChars offsetStr = DuplicateString(msg); recordOpcode(masm.currentOffset() - startOffset_, std::move(offsetStr)); } void PerfSpewer::recordOpcode(uint32_t offset, uint32_t opcode) { recordOpcode(offset, opcode, JS::UniqueChars(nullptr)); } void PerfSpewer::recordOpcode(uint32_t offset, uint32_t opcode, JS::UniqueChars&& str) { if (!irFile_) { // If we don't have a file, we can't record the opcode. return; } irFileLines_ += 1; if (!debugInfo_.emplaceBack(offset, irFileLines_)) { disable(); return; } if (str.get()) { fprintf(irFile_, "%s\n", str.get()); } else { fprintf(irFile_, "%s\n", CodeName(opcode)); } } void PerfSpewer::recordOpcode(uint32_t offset, JS::UniqueChars&& str) { recordOpcode(offset, 0, std::move(str)); } void PerfSpewer::saveDebugInfo(const char* filename, uintptr_t base, JS::JitCodeRecord* maybeProfilerRecord, AutoLockPerfSpewer& lock) { #ifdef JS_ION_PERF if (IsPerfProfiling()) { JitDumpDebugRecord debug_record = {}; uint64_t n_records = debugInfo_.length(); 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(base); debug_record.nr_entry = n_records; WriteToJitDumpFile(&debug_record, sizeof(debug_record), lock); for (DebugEntry& entry : debugInfo_) { WriteJitDumpDebugEntry(uint64_t(base) + entry.offset, filename, entry.line, entry.column, lock); } } #endif // Populate profiler record with source info if (maybeProfilerRecord) { #ifdef DEBUG uint32_t lastOffset = 0; #endif uint32_t lastLine = 0; uint32_t lastColumn = 0; for (DebugEntry& entry : debugInfo_) { MOZ_ASSERT(entry.offset >= lastOffset, "debugInfo_ must be sorted by offset"); #ifdef DEBUG lastOffset = entry.offset; #endif // Skip consecutive entries with the same line/column to reduce memory // usage and improve binary search performance. if (entry.line == lastLine && entry.column == lastColumn) { continue; } JS::JitCodeSourceInfo* srcInfo = CreateProfilerSourceEntry(maybeProfilerRecord, lock); if (!srcInfo) { return; } srcInfo->offset = entry.offset; srcInfo->lineno = entry.line; srcInfo->colno = JS::LimitedColumnNumberOneOrigin::fromUnlimited( entry.column == 0 ? 1 : entry.column); lastLine = entry.line; lastColumn = entry.column; } } } 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()->maybePartialDisplayAtom()) { funName = AtomToPrintableString( cx, script->function()->maybePartialDisplayAtom()); if (!funName) { JS_ClearPendingException(cx); } } if (stubName) { return JS_smprintf("%s: %s : %s (%s:%u:%u)", tierName, stubName, funName ? funName.get() : "*", script->filename(), script->lineno(), script->column().oneOriginValue()); } return JS_smprintf("%s: %s (%s:%u:%u)", tierName, funName ? funName.get() : "*", script->filename(), script->lineno(), script->column().oneOriginValue()); } void PerfSpewer::saveJitCodeDebugInfo(JSScript* script, JitCode* code, JS::JitCodeRecord* maybeProfilerRecord, AutoLockPerfSpewer& lock) { MOZ_ASSERT(code); // We should be done with the temp IR file, if we were using it. MOZ_ASSERT(!irFile_); if (PerfIREnabled()) { // We should have generated a debug file to use here. MOZ_ASSERT(irFileName_.get()); saveDebugInfo(irFileName_.get(), uintptr_t(code->raw()), maybeProfilerRecord, lock); return; } if (!PerfSrcEnabled() || !script || !script->filename()) { return; } saveDebugInfo(script->filename(), uintptr_t(code->raw()), maybeProfilerRecord, lock); } void PerfSpewer::saveWasmCodeDebugInfo(uintptr_t base, JS::JitCodeRecord* maybeProfilerRecord, AutoLockPerfSpewer& lock) { // We should be done with the temp IR file, if we were using it. MOZ_ASSERT(!irFile_); if (!PerfIREnabled()) { return; } saveDebugInfo(irFileName_.get(), base, maybeProfilerRecord, lock); } void PerfSpewer::saveJSProfile(JitCode* code, UniqueChars& desc, JSScript* script) { MOZ_ASSERT(code && desc); AutoLockPerfSpewer lock; if (!PerfEnabled()) { return; } JS::JitCodeRecord* maybeProfilerRecord = nullptr; if (!MaybeCreateProfilerEntry(lock, maybeProfilerRecord)) { return; // Allocation failure. } saveJitCodeDebugInfo(script, code, maybeProfilerRecord, lock); CollectJitCodeInfo(desc, code, maybeProfilerRecord, lock); } void PerfSpewer::saveWasmProfile(uintptr_t base, size_t size, UniqueChars& desc) { MOZ_ASSERT(desc); AutoLockPerfSpewer lock; if (!PerfEnabled()) { return; } JS::JitCodeRecord* maybeProfilerRecord = nullptr; if (!MaybeCreateProfilerEntry(lock, maybeProfilerRecord)) { return; // Allocation failure. } saveWasmCodeDebugInfo(base, maybeProfilerRecord, lock); PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast(base), uint64_t(size), maybeProfilerRecord, lock); } void PerfSpewer::disable(AutoLockPerfSpewer& lock) { reset(); DisablePerfSpewer(lock); } void PerfSpewer::disable() { AutoLockPerfSpewer lock; disable(lock); } void PerfSpewer::startRecording(const wasm::CodeMetadata* wasmCodeMeta) { MOZ_ASSERT(!irFile_ && !irFileName_); #ifdef JS_ION_PERF static uint32_t filenameCounter = 0; if (!IsPerfProfiling() || !PerfIREnabled()) { return; } AutoLockPerfSpewer lock; irFileName_ = JS_smprintf("%s/jitdump-ir-%u.%u%s", spew_dir.get(), filenameCounter++, getpid(), IRFileExtension()); if (!irFileName_) { disable(lock); return; } irFile_ = fopen(irFileName_.get(), "w"); if (!irFile_) { disable(lock); return; } #endif } void PerfSpewer::endRecording() { if (!irFile_) { return; } fclose(irFile_); irFile_ = nullptr; } PerfSpewer::~PerfSpewer() { // Free the allocated resources if they haven’t been freed yet. reset(); } PerfSpewer::PerfSpewer(PerfSpewer&& other) { // Can only move a PerfSpewer after endRecording(). MOZ_RELEASE_ASSERT(!irFile_ && !other.irFile_); debugInfo_ = std::move(other.debugInfo_); irFileName_ = std::move(other.irFileName_); startOffset_ = other.startOffset_; } PerfSpewer& PerfSpewer::operator=(PerfSpewer&& other) { // Can only move a PerfSpewer after endRecording(). MOZ_RELEASE_ASSERT(!irFile_ && !other.irFile_); debugInfo_ = std::move(other.debugInfo_); irFileName_ = std::move(other.irFileName_); startOffset_ = other.startOffset_; return *this; } IonICPerfSpewer::IonICPerfSpewer(JSScript* script, jsbytecode* pc) { if (!PerfSrcEnabled()) { return; } uint32_t lineno; JS::LimitedColumnNumberOneOrigin colno; lineno = PCToLineNumber(script, pc, &colno); if (!debugInfo_.emplaceBack(0, lineno, colno.oneOriginValue())) { disable(); } } void IonICPerfSpewer::saveProfile(JSContext* cx, JSScript* script, JitCode* code, const char* stubName) { if (!PerfEnabled()) { return; } UniqueChars desc = GetFunctionDesc("IonIC", cx, script, stubName); if (!desc) { disable(); return; } PerfSpewer::saveJSProfile(code, desc, script); } void BaselineICPerfSpewer::saveProfile(JitCode* code, const char* stubName) { if (!PerfEnabled()) { return; } UniqueChars desc = JS_smprintf("BaselineIC: %s", stubName); if (!desc) { disable(); return; } PerfSpewer::saveJSProfile(code, desc, nullptr); } void BaselinePerfSpewer::saveProfile(JSContext* cx, JSScript* script, JitCode* code) { if (!PerfEnabled()) { return; } UniqueChars desc = GetFunctionDesc("Baseline", cx, script); if (!desc) { disable(); return; } PerfSpewer::saveJSProfile(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) { for (Op& entry : ops_) { recordOpcode(entry.offset, entry.opcode, std::move(entry.str)); } ops_.clear(); UniqueChars desc = DuplicateString("BaselineInterpreter"); if (!desc) { return; } PerfSpewer::saveJSProfile(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 < ops_.length(); i++) { uintptr_t base = uintptr_t(code->raw()) + ops_[i - 1].offset; uintptr_t size = ops_[i].offset - ops_[i - 1].offset; UniqueChars rangeName; if (ops_[i - 1].str) { rangeName = JS_smprintf("BlinterpOp: %s", ops_[i - 1].str.get()); } else { rangeName = JS_smprintf("BlinterpOp: %s", CodeName(ops_[i - 1].opcode)); } // If rangeName is empty, we probably went OOM. if (!rangeName) { disable(); return; } MOZ_ASSERT(base + size <= uintptr_t(code->raw()) + code->instructionsSize()); CollectPerfSpewerJitCodeProfile(base, size, rangeName.get()); } } void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm, const JSOp& op) { if (!PerfEnabled()) { return; } if (!ops_.emplaceBack(masm.currentOffset() - startOffset_, unsigned(op))) { disable(); ops_.clear(); return; } } void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler& masm, const char* name) { if (!PerfEnabled()) { return; } UniqueChars desc = DuplicateString(name); if (!ops_.emplaceBack(masm.currentOffset() - startOffset_, std::move(desc))) { disable(); ops_.clear(); return; } } void IonPerfSpewer::saveJSProfile(JSContext* cx, JSScript* script, JitCode* code) { if (!PerfEnabled()) { return; } UniqueChars desc = GetFunctionDesc("Ion", cx, script); if (!desc) { disable(); return; } PerfSpewer::saveJSProfile(code, desc, script); } void IonPerfSpewer::saveWasmProfile(uintptr_t codeBase, size_t codeSize, UniqueChars& desc) { if (!PerfEnabled()) { return; } PerfSpewer::saveWasmProfile(codeBase, codeSize, desc); } void WasmBaselinePerfSpewer::saveProfile(uintptr_t codeBase, size_t codeSize, UniqueChars& desc) { if (!PerfEnabled()) { return; } PerfSpewer::saveWasmProfile(codeBase, codeSize, desc); } 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* maybeProfilerRecord = nullptr; if (!MaybeCreateProfilerEntry(lock, maybeProfilerRecord)) { return; // Allocation failure. } UniqueChars desc = JS_smprintf("%s", msg); if (!desc) { DisablePerfSpewer(lock); return; } PerfSpewer::CollectJitCodeInfo(desc, code, maybeProfilerRecord, lock); } } void js::jit::CollectPerfSpewerJitCodeProfile(uintptr_t base, uint64_t size, const char* msg) { if (!PerfEnabled()) { return; } if (size > 0) { AutoLockPerfSpewer lock; JS::JitCodeRecord* maybeProfilerRecord = nullptr; if (!MaybeCreateProfilerEntry(lock, maybeProfilerRecord)) { return; // Allocation failure. } UniqueChars desc = JS_smprintf("%s", msg); if (!desc) { DisablePerfSpewer(lock); return; } PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast(base), size, maybeProfilerRecord, lock); } } void js::jit::CollectPerfSpewerWasmMap(uintptr_t base, uintptr_t size, UniqueChars&& desc) { if (size == 0U || !PerfEnabled()) { return; } AutoLockPerfSpewer lock; JS::JitCodeRecord* maybeProfilerRecord = nullptr; if (!MaybeCreateProfilerEntry(lock, maybeProfilerRecord)) { return; // Allocation failure. } PerfSpewer::CollectJitCodeInfo(desc, reinterpret_cast(base), uint64_t(size), maybeProfilerRecord, lock); } void js::jit::PerfSpewerRangeRecorder::appendEntry(UniqueChars& desc) { if (!ranges.append(std::make_pair(masm.currentOffset(), std::move(desc)))) { DisablePerfSpewer(); ranges.clear(); } } void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name) { if (!PerfEnabled()) { return; } UniqueChars desc = DuplicateString(name); if (!desc) { DisablePerfSpewer(); return; } appendEntry(desc); } void js::jit::PerfSpewerRangeRecorder::recordVMWrapperOffset(const char* name) { if (!PerfEnabled()) { return; } UniqueChars desc = JS_smprintf("VMWrapper: %s", name); if (!desc) { DisablePerfSpewer(); return; } appendEntry(desc); } void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name, JSContext* cx, JSScript* script) { if (!PerfEnabled()) { return; } UniqueChars desc = GetFunctionDesc(name, cx, script); if (!desc) { DisablePerfSpewer(); return; } 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(); }