1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/IntegerPrintfMacros.h"
8 #include "mozilla/Printf.h"
10 #if defined(JS_ION_PERF) && defined(XP_UNIX)
12 # include <sys/mman.h>
13 # include <sys/stat.h>
17 #if defined(JS_ION_PERF) && defined(XP_LINUX) && !defined(ANDROID) && \
20 # include <sys/syscall.h>
21 # include <sys/types.h>
23 # define gettid() static_cast<pid_t>(syscall(__NR_gettid))
26 #if defined(JS_ION_PERF) && (defined(ANDROID) || defined(XP_DARWIN))
30 char* get_current_dir_name() {
31 char* buffer
= (char*)malloc(PATH_MAX
* sizeof(char));
32 if (buffer
== nullptr) {
36 if (getcwd(buffer
, PATH_MAX
) == nullptr) {
45 #if defined(JS_ION_PERF) && defined(XP_DARWIN)
49 pid_t
gettid_pthread() {
51 if (pthread_threadid_np(nullptr, &tid
) != 0) {
54 // Truncate the tid to 32 bits. macOS thread IDs are usually small enough.
55 // And even if we do end up truncating, it doesn't matter much for Jitdump
56 // as long as the process ID is correct.
59 # define gettid() gettid_pthread()
62 #include "jit/PerfSpewer.h"
66 #include "jit/Jitdump.h"
67 #include "jit/JitSpewer.h"
70 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset
71 #include "js/JitCodeAPI.h"
72 #include "js/Printf.h"
73 #include "vm/BytecodeUtil.h"
74 #include "vm/MutexIDs.h"
79 # include <evntprov.h>
84 const GUID PROVIDER_JSCRIPT9
= {
88 {0xbd, 0xba, 0x0a, 0xc6, 0xe4, 0x5d, 0xa5, 0x6c}};
89 const EVENT_DESCRIPTOR MethodLoad
= {0x9, 0x0, 0x0, 0x4, 0xa, 0x1, 0x1};
91 static REGHANDLE sETWRegistrationHandle
= NULL
;
93 static std::atomic
<bool> etwCollection
= false;
97 using namespace js::jit
;
99 enum class PerfModeType
{ None
, Function
, Source
, IR
, IROperands
};
101 static std::atomic
<bool> geckoProfiling
= false;
102 static std::atomic
<PerfModeType
> PerfMode
= PerfModeType::None
;
104 // Mutex to guard access to the profiler vectors and jitdump file if perf
105 // profiling is enabled.
106 static js::Mutex
PerfMutex(mutexid::PerfSpewer
);
108 static PersistentRooted
<GCVector
<JitCode
*, 0, js::SystemAllocPolicy
>>
110 static ProfilerJitCodeVector profilerData
;
112 static bool IsGeckoProfiling() { return geckoProfiling
; }
114 static UniqueChars spew_dir
;
115 static FILE* JitDumpFilePtr
= nullptr;
116 static void* mmap_address
= nullptr;
117 static bool IsPerfProfiling() { return JitDumpFilePtr
!= nullptr; }
120 AutoLockPerfSpewer::AutoLockPerfSpewer() { PerfMutex
.lock(); }
122 AutoLockPerfSpewer::~AutoLockPerfSpewer() { PerfMutex
.unlock(); }
125 static uint64_t GetMonotonicTimestamp() {
126 using mozilla::TimeStamp
;
128 return TimeStamp::Now().RawClockMonotonicNanosecondsSinceBoot();
130 return TimeStamp::Now().RawQueryPerformanceCounterValue().value();
132 return TimeStamp::Now().RawMachAbsoluteTimeNanoseconds();
134 MOZ_CRASH("no timestamp");
138 // values are from /usr/include/elf.h
139 static uint32_t GetMachineEncoding() {
140 # if defined(JS_CODEGEN_X86)
142 # elif defined(JS_CODEGEN_X64)
143 return 62; // EM_X86_64
144 # elif defined(JS_CODEGEN_ARM)
146 # elif defined(JS_CODEGEN_ARM64)
147 return 183; // EM_AARCH64
148 # elif defined(JS_CODEGEN_MIPS32)
150 # elif defined(JS_CODEGEN_MIPS64)
153 return 0; // Unsupported
157 static void WriteToJitDumpFile(const void* addr
, uint32_t size
,
158 AutoLockPerfSpewer
& lock
) {
159 MOZ_RELEASE_ASSERT(JitDumpFilePtr
);
160 size_t rv
= fwrite(addr
, 1, size
, JitDumpFilePtr
);
161 MOZ_RELEASE_ASSERT(rv
== size
);
164 static void WriteJitDumpDebugEntry(uint64_t addr
, const char* filename
,
166 JS::LimitedColumnNumberOneOrigin colno
,
167 AutoLockPerfSpewer
& lock
) {
168 JitDumpDebugEntry entry
= {addr
, lineno
, colno
.oneOriginValue()};
169 WriteToJitDumpFile(&entry
, sizeof(entry
), lock
);
170 WriteToJitDumpFile(filename
, strlen(filename
) + 1, lock
);
173 static void writeJitDumpHeader(AutoLockPerfSpewer
& lock
) {
174 JitDumpHeader header
= {};
175 header
.magic
= 0x4A695444;
177 header
.total_size
= sizeof(header
);
178 header
.elf_mach
= GetMachineEncoding();
180 header
.pid
= getpid();
181 header
.timestamp
= GetMonotonicTimestamp();
184 WriteToJitDumpFile(&header
, sizeof(header
), lock
);
187 static bool openJitDump() {
188 if (JitDumpFilePtr
) {
191 AutoLockPerfSpewer lock
;
193 const ssize_t bufferSize
= 256;
194 char filenameBuffer
[bufferSize
];
196 // We want to write absolute filenames into the debug info or else we get the
197 // filenames in the perf report
198 if (getenv("PERF_SPEW_DIR")) {
199 char* env_dir
= getenv("PERF_SPEW_DIR");
200 if (env_dir
[0] == '/') {
201 spew_dir
= JS_smprintf("%s", env_dir
);
203 const char* dir
= get_current_dir_name();
205 fprintf(stderr
, "couldn't get current dir name\n");
208 spew_dir
= JS_smprintf("%s/%s", dir
, env_dir
);
212 fprintf(stderr
, "Please define PERF_SPEW_DIR as an output directory.\n");
216 if (snprintf(filenameBuffer
, bufferSize
, "%s/jit-%d.dump", spew_dir
.get(),
217 getpid()) >= bufferSize
) {
221 MOZ_ASSERT(!JitDumpFilePtr
);
223 int fd
= open(filenameBuffer
, O_CREAT
| O_TRUNC
| O_RDWR
, 0666);
224 JitDumpFilePtr
= fdopen(fd
, "w+");
226 if (!JitDumpFilePtr
) {
231 // We need to mmap the jitdump file for perf to find it.
232 long page_size
= sysconf(_SC_PAGESIZE
);
233 int prot
= PROT_READ
| PROT_EXEC
;
234 // The mmap call fails on some Android devices if PROT_EXEC is specified, and
235 // it does not appear to be required by simpleperf, so omit it on Android.
239 mmap_address
= mmap(nullptr, page_size
, prot
, MAP_PRIVATE
, fd
, 0);
240 if (mmap_address
== MAP_FAILED
) {
241 PerfMode
= PerfModeType::None
;
246 writeJitDumpHeader(lock
);
250 static void CheckPerf() {
251 static bool PerfChecked
= false;
254 const char* env
= getenv("IONPERF");
255 if (env
== nullptr) {
256 PerfMode
= PerfModeType::None
;
258 "Warning: JIT perf reporting requires IONPERF set to \"func\" "
259 ", \"src\" or \"ir\". ");
260 fprintf(stderr
, "Perf mapping will be deactivated.\n");
261 } else if (!strcmp(env
, "src")) {
262 PerfMode
= PerfModeType::Source
;
263 } else if (!strcmp(env
, "ir")) {
264 PerfMode
= PerfModeType::IR
;
265 } else if (!strcmp(env
, "ir-ops")) {
267 PerfMode
= PerfModeType::IROperands
;
270 "Warning: IONPERF=ir-ops requires --enable-jitspew to be "
271 "enabled, defaulting to IONPERF=ir\n");
272 PerfMode
= PerfModeType::IR
;
274 } else if (!strcmp(env
, "func")) {
275 PerfMode
= PerfModeType::Function
;
277 fprintf(stderr
, "Use IONPERF=func to record at function granularity\n");
279 "Use IONPERF=ir to record and annotate assembly with IR\n");
281 "Use IONPERF=src to record and annotate assembly with source, if "
282 "available locally\n");
286 if (PerfMode
!= PerfModeType::None
) {
292 fprintf(stderr
, "Failed to open perf map file. Disabling IONPERF.\n");
293 PerfMode
= PerfModeType::None
;
301 void NTAPI
ETWEnableCallback(LPCGUID aSourceId
, ULONG aIsEnabled
, UCHAR aLevel
,
302 ULONGLONG aMatchAnyKeyword
,
303 ULONGLONG aMatchAllKeyword
,
304 PEVENT_FILTER_DESCRIPTOR aFilterData
,
305 PVOID aCallbackContext
) {
306 // This is called on a CRT worker thread. This means this might race with
307 // our main thread, but that is okay.
308 etwCollection
= aIsEnabled
;
309 PerfMode
= aIsEnabled
? PerfModeType::Function
: PerfModeType::None
;
313 static bool sHasRegisteredETW
= false;
314 if (!sHasRegisteredETW
) {
315 if (getenv("ETW_ENABLED")) {
316 EventRegister(&PROVIDER_JSCRIPT9
, // GUID that identifies the provider
317 ETWEnableCallback
, // Callback for enabling collection
318 NULL
, // Context not used
319 &sETWRegistrationHandle
// Used when calling EventWrite
320 // and EventUnregister
323 sHasRegisteredETW
= true;
329 void PerfSpewer::Init() {
338 static void DisablePerfSpewer(AutoLockPerfSpewer
& lock
) {
339 fprintf(stderr
, "Warning: Disabling PerfSpewer.");
341 geckoProfiling
= false;
343 etwCollection
= false;
345 PerfMode
= PerfModeType::None
;
347 long page_size
= sysconf(_SC_PAGESIZE
);
348 munmap(mmap_address
, page_size
);
349 fclose(JitDumpFilePtr
);
350 JitDumpFilePtr
= nullptr;
354 void js::jit::ResetPerfSpewer(bool enabled
) {
355 AutoLockPerfSpewer lock
;
357 profilerData
.clear();
358 jitCodeVector
.clear();
359 geckoProfiling
= enabled
;
362 static JS::JitCodeRecord
* CreateProfilerEntry(AutoLockPerfSpewer
& lock
) {
363 if (!IsGeckoProfiling()) {
367 if (!profilerData
.growBy(1)) {
368 DisablePerfSpewer(lock
);
371 return &profilerData
.back();
374 static JS::JitCodeIRInfo
* CreateProfilerIREntry(JS::JitCodeRecord
* record
,
375 AutoLockPerfSpewer
& lock
) {
380 if (!record
->irInfo
.growBy(1)) {
381 DisablePerfSpewer(lock
);
384 return &record
->irInfo
.back();
387 static JS::JitCodeSourceInfo
* CreateProfilerSourceEntry(
388 JS::JitCodeRecord
* record
, AutoLockPerfSpewer
& lock
) {
393 if (!record
->sourceInfo
.growBy(1)) {
394 DisablePerfSpewer(lock
);
397 return &record
->sourceInfo
.back();
400 JS::JitOpcodeDictionary::JitOpcodeDictionary() {
401 MOZ_ASSERT(JS_IsInitialized());
403 #define COPY_JSOP_OPCODE(name, ...) \
404 if (!baselineDictionary.append(JS_smprintf(#name))) { \
407 FOR_EACH_OPCODE(COPY_JSOP_OPCODE
)
408 #undef COPY_JSOP_OPCODE
410 #define COPY_LIR_OPCODE(name, ...) \
411 if (!ionDictionary.append(JS_smprintf(#name))) { \
414 LIR_OPCODE_LIST(COPY_LIR_OPCODE
)
415 #undef COPY_LIR_OPCODE
417 #define COPY_CACHEIR_OPCODE(name, ...) \
418 if (!icDictionary.append(JS_smprintf(#name))) { \
421 CACHE_IR_OPS(COPY_CACHEIR_OPCODE
)
422 #undef COPY_CACHEIR_OPCODE
425 // API to access JitCode data for the Gecko Profiler.
426 void JS::JitCodeIterator::getDataForIndex(size_t IteratorIndex
) {
427 if (IteratorIndex
>= profilerData
.length()) {
430 data
= &profilerData
[IteratorIndex
];
434 JS::JitCodeIterator::JitCodeIterator() : iteratorIndex(0) {
435 MOZ_ASSERT(JS_IsInitialized());
440 JS::JitCodeIterator::~JitCodeIterator() { PerfMutex
.unlock(); }
442 static bool PerfSrcEnabled() {
443 return PerfMode
== PerfModeType::Source
|| geckoProfiling
;
447 static bool PerfIROpsEnabled() { return PerfMode
== PerfModeType::IROperands
; }
450 static bool PerfIREnabled() {
451 return (PerfMode
== PerfModeType::IROperands
) ||
452 (PerfMode
== PerfModeType::IR
) || geckoProfiling
;
455 static bool PerfFuncEnabled() {
456 return PerfMode
== PerfModeType::Function
|| geckoProfiling
;
459 bool js::jit::PerfEnabled() {
460 return PerfSrcEnabled() || PerfIREnabled() || PerfFuncEnabled();
463 void InlineCachePerfSpewer::recordInstruction(MacroAssembler
& masm
,
465 if (!PerfIREnabled()) {
468 AutoLockPerfSpewer lock
;
470 if (!opcodes_
.emplaceBack(masm
.currentOffset(), static_cast<unsigned>(op
))) {
472 DisablePerfSpewer(lock
);
476 #define CHECK_RETURN(x) \
478 AutoLockPerfSpewer lock; \
479 DisablePerfSpewer(lock); \
483 void IonPerfSpewer::recordInstruction(MacroAssembler
& masm
, LInstruction
* ins
) {
484 if (!PerfIREnabled() && !PerfSrcEnabled()) {
488 LNode::Opcode op
= ins
->op();
489 UniqueChars opcodeStr
;
491 jsbytecode
* bytecodepc
= nullptr;
492 if (MDefinition
* mir
= ins
->mirRaw()) {
493 bytecodepc
= mir
->trackedSite()->pc();
497 if (PerfIROpsEnabled()) {
499 CHECK_RETURN(buf
.init());
500 buf
.put(LIRCodeName(op
));
501 ins
->printOperands(buf
);
502 opcodeStr
= buf
.release();
505 if (!opcodes_
.emplaceBack(masm
.currentOffset(), static_cast<unsigned>(op
),
506 opcodeStr
, bytecodepc
)) {
508 AutoLockPerfSpewer lock
;
509 DisablePerfSpewer(lock
);
514 static void PrintStackValue(JSContext
* cx
, StackValue
* stackVal
,
515 CompilerFrameInfo
& frame
, Sprinter
& buf
) {
516 switch (stackVal
->kind()) {
517 /****** Constant ******/
518 case StackValue::Constant
: {
519 js::Value constantVal
= stackVal
->constant();
520 if (constantVal
.isInt32()) {
521 buf
.printf("%d", constantVal
.toInt32());
522 } else if (constantVal
.isObjectOrNull()) {
523 buf
.printf("obj:%p", constantVal
.toObjectOrNull());
524 } else if (constantVal
.isString()) {
526 buf
.putString(cx
, constantVal
.toString());
527 } else if (constantVal
.isNumber()) {
528 buf
.printf("num:%f", constantVal
.toNumber());
529 } else if (constantVal
.isSymbol()) {
531 constantVal
.toSymbol()->dump(buf
);
533 buf
.printf("raw:%" PRIx64
, constantVal
.asRawBits());
536 /****** Register ******/
537 case StackValue::Register
: {
538 Register reg
= stackVal
->reg().payloadOrValueReg();
541 /****** Stack ******/
542 case StackValue::Stack
:
545 /****** ThisSlot ******/
546 case StackValue::ThisSlot
: {
547 # ifdef JS_HAS_HIDDEN_SP
550 Address addr
= frame
.addressOfThis();
551 buf
.printf("this:%s(%d)", addr
.base
.name(), addr
.offset
);
554 /****** LocalSlot ******/
555 case StackValue::LocalSlot
:
556 buf
.printf("local:%u", stackVal
->localSlot());
558 /****** ArgSlot ******/
559 case StackValue::ArgSlot
:
560 buf
.printf("arg:%u", stackVal
->argSlot());
564 MOZ_CRASH("Unexpected kind");
570 void BaselinePerfSpewer::recordInstruction(JSContext
* cx
, MacroAssembler
& masm
,
572 CompilerFrameInfo
& frame
) {
573 if (!PerfIREnabled() && !PerfSrcEnabled()) {
578 UniqueChars opcodeStr
;
581 if (PerfIROpsEnabled()) {
582 JSScript
* script
= frame
.script
;
583 unsigned numOperands
= js::StackUses(op
, pc
);
586 CHECK_RETURN(buf
.init());
587 buf
.put(js::CodeName(op
));
593 case JSOp::BindGName
:
595 case JSOp::GetGName
: {
596 // Emit the name used for these ops
597 Rooted
<PropertyName
*> name(cx
, script
->getName(pc
));
599 buf
.putString(cx
, name
);
605 // Output should be "JSOp (operand1), (operand2), ..."
606 for (unsigned i
= 1; i
<= numOperands
; i
++) {
608 StackValue
* stackVal
= frame
.peek(-int(i
));
609 PrintStackValue(cx
, stackVal
, frame
, buf
);
611 if (i
< numOperands
) {
617 opcodeStr
= buf
.release();
621 if (!opcodes_
.emplaceBack(masm
.currentOffset(), static_cast<unsigned>(op
),
624 AutoLockPerfSpewer lock
;
625 DisablePerfSpewer(lock
);
629 const char* BaselinePerfSpewer::CodeName(unsigned op
) {
630 return js::CodeName(static_cast<JSOp
>(op
));
633 const char* BaselineInterpreterPerfSpewer::CodeName(unsigned op
) {
634 return js::CodeName(static_cast<JSOp
>(op
));
637 const char* IonPerfSpewer::CodeName(unsigned op
) {
638 return js::jit::LIRCodeName(static_cast<LNode::Opcode
>(op
));
640 const char* InlineCachePerfSpewer::CodeName(unsigned op
) {
641 return js::jit::CacheIRCodeName(static_cast<CacheOp
>(op
));
644 void PerfSpewer::CollectJitCodeInfo(UniqueChars
& function_name
, JitCode
* code
,
645 JS::JitCodeRecord
* profilerRecord
,
646 AutoLockPerfSpewer
& lock
) {
647 // Hold the JitCode objects here so they are not GC'd during profiling.
648 if (IsGeckoProfiling()) {
649 if (!jitCodeVector
.append(code
)) {
650 DisablePerfSpewer(lock
);
654 CollectJitCodeInfo(function_name
, reinterpret_cast<void*>(code
->raw()),
655 code
->instructionsSize(), profilerRecord
, lock
);
658 void PerfSpewer::CollectJitCodeInfo(UniqueChars
& function_name
, void* code_addr
,
660 JS::JitCodeRecord
* profilerRecord
,
661 AutoLockPerfSpewer
& lock
) {
663 static uint64_t codeIndex
= 1;
665 if (IsPerfProfiling()) {
666 JitDumpLoadRecord record
= {};
668 record
.header
.id
= JIT_CODE_LOAD
;
669 record
.header
.total_size
=
670 sizeof(record
) + strlen(function_name
.get()) + 1 + code_size
;
671 record
.header
.timestamp
= GetMonotonicTimestamp();
672 record
.pid
= getpid();
673 record
.tid
= gettid();
674 record
.vma
= uint64_t(code_addr
);
675 record
.code_addr
= uint64_t(code_addr
);
676 record
.code_size
= code_size
;
677 record
.code_index
= codeIndex
++;
679 WriteToJitDumpFile(&record
, sizeof(record
), lock
);
680 WriteToJitDumpFile(function_name
.get(), strlen(function_name
.get()) + 1,
682 WriteToJitDumpFile(code_addr
, code_size
, lock
);
687 void* scriptContextId
= NULL
;
690 uint64_t assembly
= 0;
691 uint32_t line_col
= 0;
694 int name_len
= strlen(function_name
.get());
695 std::wstring
name(name_len
+ 1, '\0');
696 if (MultiByteToWideChar(CP_UTF8
, 0, function_name
.get(), name_len
,
697 name
.data(), name
.size()) == 0) {
698 DisablePerfSpewer(lock
);
702 EVENT_DATA_DESCRIPTOR EventData
[10];
704 EventDataDescCreate(&EventData
[0], &scriptContextId
, sizeof(PVOID
));
705 EventDataDescCreate(&EventData
[1], &code_addr
, sizeof(PVOID
));
706 EventDataDescCreate(&EventData
[2], &code_size
, sizeof(unsigned __int64
));
707 EventDataDescCreate(&EventData
[3], &method
, sizeof(uint32_t));
708 EventDataDescCreate(&EventData
[4], &flags
, sizeof(const unsigned short));
709 EventDataDescCreate(&EventData
[5], &map
, sizeof(const unsigned short));
710 EventDataDescCreate(&EventData
[6], &assembly
, sizeof(unsigned __int64
));
711 EventDataDescCreate(&EventData
[7], &line_col
, sizeof(const unsigned int));
712 EventDataDescCreate(&EventData
[8], &line_col
, sizeof(const unsigned int));
713 EventDataDescCreate(&EventData
[9], name
.c_str(),
714 sizeof(wchar_t) * (name
.length() + 1));
716 ULONG result
= EventWrite(
717 sETWRegistrationHandle
, // From EventRegister
718 &MethodLoad
, // EVENT_DESCRIPTOR generated from the manifest
719 (ULONG
)10, // Size of the array of EVENT_DATA_DESCRIPTORs
720 EventData
// Array of descriptors that contain the event data
723 if (result
!= ERROR_SUCCESS
) {
724 DisablePerfSpewer(lock
);
730 if (IsGeckoProfiling()) {
731 profilerRecord
->instructionSize
= code_size
;
732 profilerRecord
->code_addr
= uint64_t(code_addr
);
733 profilerRecord
->functionName
= std::move(function_name
);
737 void PerfSpewer::recordOffset(MacroAssembler
& masm
, const char* msg
) {
738 if (!PerfIREnabled()) {
742 UniqueChars offsetStr
= DuplicateString(msg
);
743 if (!opcodes_
.emplaceBack(masm
.currentOffset(), offsetStr
)) {
745 AutoLockPerfSpewer lock
;
746 DisablePerfSpewer(lock
);
750 void PerfSpewer::saveJitCodeIRInfo(JitCode
* code
,
751 JS::JitCodeRecord
* profilerRecord
,
752 AutoLockPerfSpewer
& lock
) {
754 static uint32_t filenameCounter
= 0;
755 UniqueChars scriptFilename
;
756 FILE* scriptFile
= nullptr;
758 if (IsPerfProfiling()) {
759 scriptFilename
= JS_smprintf("%s/jitdump-script-%u.%u.txt", spew_dir
.get(),
760 filenameCounter
++, getpid());
761 scriptFile
= fopen(scriptFilename
.get(), "w");
763 DisablePerfSpewer(lock
);
767 JitDumpDebugRecord debug_record
= {};
768 uint64_t n_records
= opcodes_
.length();
770 debug_record
.header
.id
= JIT_CODE_DEBUG_INFO
;
771 debug_record
.header
.total_size
=
772 sizeof(debug_record
) + n_records
* (sizeof(JitDumpDebugEntry
) +
773 strlen(scriptFilename
.get()) + 1);
774 debug_record
.header
.timestamp
= GetMonotonicTimestamp();
775 debug_record
.code_addr
= uint64_t(code
->raw());
776 debug_record
.nr_entry
= n_records
;
778 WriteToJitDumpFile(&debug_record
, sizeof(debug_record
), lock
);
782 if (profilerRecord
) {
783 profilerRecord
->tier
= GetTier();
786 for (size_t i
= 0; i
< opcodes_
.length(); i
++) {
787 OpcodeEntry
& entry
= opcodes_
[i
];
789 if (IsPerfProfiling()) {
790 // If a string was recorded for this offset, use that instead.
792 fprintf(scriptFile
, "%s\n", entry
.str
.get());
794 fprintf(scriptFile
, "%s\n", CodeName(entry
.opcode
));
796 uint64_t addr
= uint64_t(code
->raw()) + entry
.offset
;
797 uint64_t lineno
= i
+ 1;
798 WriteJitDumpDebugEntry(addr
, scriptFilename
.get(), lineno
,
799 JS::LimitedColumnNumberOneOrigin(), lock
);
803 if (JS::JitCodeIRInfo
* irInfo
=
804 CreateProfilerIREntry(profilerRecord
, lock
)) {
805 irInfo
->offset
= entry
.offset
;
806 irInfo
->opcode
= entry
.opcode
;
807 // Profiler API now owns this string, if defined.
808 irInfo
->str
= std::move(entry
.str
);
814 if (IsPerfProfiling()) {
820 void PerfSpewer::saveJitCodeSourceInfo(JSScript
* script
, JitCode
* code
,
821 JS::JitCodeRecord
* profilerRecord
,
822 AutoLockPerfSpewer
& lock
) {
823 const char* filename
= script
->filename();
829 bool perfProfiling
= IsPerfProfiling();
832 JitDumpDebugRecord debug_record
= {};
834 uint64_t n_records
= 0;
835 for (OpcodeEntry
& entry
: opcodes_
) {
836 if (entry
.bytecodepc
) {
841 debug_record
.header
.id
= JIT_CODE_DEBUG_INFO
;
842 debug_record
.header
.total_size
=
843 sizeof(debug_record
) +
844 n_records
* (sizeof(JitDumpDebugEntry
) + strlen(filename
) + 1);
845 debug_record
.header
.timestamp
= GetMonotonicTimestamp();
846 debug_record
.code_addr
= uint64_t(code
->raw());
847 debug_record
.nr_entry
= n_records
;
849 WriteToJitDumpFile(&debug_record
, sizeof(debug_record
), lock
);
853 JS::LimitedColumnNumberOneOrigin colno
;
855 for (OpcodeEntry
& entry
: opcodes_
) {
856 jsbytecode
* pc
= entry
.bytecodepc
;
860 // We could probably make this a bit faster by caching the previous pc
861 // offset, but it currently doesn't seem noticeable when testing.
862 lineno
= PCToLineNumber(script
, pc
, &colno
);
864 if (JS::JitCodeSourceInfo
* srcInfo
=
865 CreateProfilerSourceEntry(profilerRecord
, lock
)) {
866 srcInfo
->offset
= entry
.offset
;
867 srcInfo
->lineno
= lineno
;
868 srcInfo
->colno
= colno
;
869 srcInfo
->filename
= JS_smprintf("%s", filename
);
874 WriteJitDumpDebugEntry(uint64_t(code
->raw()) + entry
.offset
, filename
,
875 lineno
, colno
, lock
);
881 static UniqueChars
GetFunctionDesc(const char* tierName
, JSContext
* cx
,
883 const char* stubName
= nullptr) {
884 MOZ_ASSERT(script
&& tierName
&& cx
);
886 if (script
->function() && script
->function()->maybePartialDisplayAtom()) {
887 funName
= AtomToPrintableString(
888 cx
, script
->function()->maybePartialDisplayAtom());
892 return JS_smprintf("%s: %s : %s (%s:%u:%u)", tierName
, stubName
,
893 funName
? funName
.get() : "*", script
->filename(),
894 script
->lineno(), script
->column().oneOriginValue());
896 return JS_smprintf("%s: %s (%s:%u:%u)", tierName
,
897 funName
? funName
.get() : "*", script
->filename(),
898 script
->lineno(), script
->column().oneOriginValue());
901 void PerfSpewer::saveDebugInfo(JSScript
* script
, JitCode
* code
,
902 JS::JitCodeRecord
* profilerRecord
,
903 AutoLockPerfSpewer
& lock
) {
905 if (PerfIREnabled()) {
906 saveJitCodeIRInfo(code
, profilerRecord
, lock
);
907 } else if (PerfSrcEnabled() && script
) {
908 saveJitCodeSourceInfo(script
, code
, profilerRecord
, lock
);
912 void PerfSpewer::saveProfile(JitCode
* code
, UniqueChars
& desc
,
914 MOZ_ASSERT(PerfEnabled());
915 MOZ_ASSERT(code
&& desc
);
916 AutoLockPerfSpewer lock
;
917 JS::JitCodeRecord
* profilerRecord
= CreateProfilerEntry(lock
);
919 saveDebugInfo(script
, code
, profilerRecord
, lock
);
920 CollectJitCodeInfo(desc
, code
, profilerRecord
, lock
);
923 IonICPerfSpewer::IonICPerfSpewer(jsbytecode
* pc
) {
924 if (!opcodes_
.emplaceBack(pc
)) {
925 AutoLockPerfSpewer lock
;
927 DisablePerfSpewer(lock
);
931 void IonICPerfSpewer::saveJitCodeSourceInfo(JSScript
* script
, JitCode
* code
,
932 JS::JitCodeRecord
* profilerRecord
,
933 AutoLockPerfSpewer
& lock
) {
935 MOZ_ASSERT(opcodes_
.length() == 1);
936 jsbytecode
* pc
= opcodes_
[0].bytecodepc
;
942 const char* filename
= script
->filename();
947 if (!IsPerfProfiling()) {
951 JitDumpDebugRecord debug_record
= {};
952 uint64_t n_records
= 1;
954 debug_record
.header
.id
= JIT_CODE_DEBUG_INFO
;
955 debug_record
.header
.total_size
=
956 sizeof(debug_record
) +
957 n_records
* (sizeof(JitDumpDebugEntry
) + strlen(filename
) + 1);
959 debug_record
.header
.timestamp
= GetMonotonicTimestamp();
960 debug_record
.code_addr
= uint64_t(code
->raw());
961 debug_record
.nr_entry
= n_records
;
963 WriteToJitDumpFile(&debug_record
, sizeof(debug_record
), lock
);
966 JS::LimitedColumnNumberOneOrigin colno
;
967 lineno
= PCToLineNumber(script
, pc
, &colno
);
969 WriteJitDumpDebugEntry(uint64_t(code
->raw()), filename
, lineno
, colno
, lock
);
973 void IonICPerfSpewer::saveProfile(JSContext
* cx
, JSScript
* script
,
974 JitCode
* code
, const char* stubName
) {
975 if (!PerfEnabled()) {
978 UniqueChars desc
= GetFunctionDesc("IonIC", cx
, script
, stubName
);
979 PerfSpewer::saveProfile(code
, desc
, script
);
982 void BaselineICPerfSpewer::saveProfile(JitCode
* code
, const char* stubName
) {
983 if (!PerfEnabled()) {
986 UniqueChars desc
= JS_smprintf("BaselineIC: %s", stubName
);
987 PerfSpewer::saveProfile(code
, desc
, nullptr);
990 void BaselinePerfSpewer::saveProfile(JSContext
* cx
, JSScript
* script
,
992 if (!PerfEnabled()) {
995 UniqueChars desc
= GetFunctionDesc("Baseline", cx
, script
);
996 PerfSpewer::saveProfile(code
, desc
, script
);
999 void BaselineInterpreterPerfSpewer::saveProfile(JitCode
* code
) {
1000 if (!PerfEnabled()) {
1004 enum class SpewKind
{ Uninitialized
, SingleSym
, MultiSym
};
1006 // Check which type of Baseline Interpreter Spew is requested.
1007 static SpewKind kind
= SpewKind::Uninitialized
;
1008 if (kind
== SpewKind::Uninitialized
) {
1009 if (getenv("IONPERF_SINGLE_BLINTERP")) {
1010 kind
= SpewKind::SingleSym
;
1012 kind
= SpewKind::MultiSym
;
1016 // For SingleSym, just emit one "BaselineInterpreter" symbol
1017 // and emit the opcodes as IR if IONPERF=ir is used.
1018 if (kind
== SpewKind::SingleSym
) {
1019 UniqueChars desc
= DuplicateString("BaselineInterpreter");
1020 PerfSpewer::saveProfile(code
, desc
, nullptr);
1024 // For MultiSym, split up each opcode into its own symbol.
1025 // No IR is emitted in this case, so we can skip PerfSpewer::saveProfile.
1026 MOZ_ASSERT(kind
== SpewKind::MultiSym
);
1027 for (size_t i
= 1; i
< opcodes_
.length(); i
++) {
1028 uintptr_t base
= uintptr_t(code
->raw()) + opcodes_
[i
- 1].offset
;
1029 uintptr_t size
= opcodes_
[i
].offset
- opcodes_
[i
- 1].offset
;
1031 UniqueChars rangeName
;
1032 if (opcodes_
[i
- 1].str
) {
1033 rangeName
= JS_smprintf("BlinterpOp: %s", opcodes_
[i
- 1].str
.get());
1036 JS_smprintf("BlinterpOp: %s", CodeName(opcodes_
[i
- 1].opcode
));
1039 // If rangeName is empty, we probably went OOM.
1041 AutoLockPerfSpewer lock
;
1042 DisablePerfSpewer(lock
);
1046 MOZ_ASSERT(base
+ size
<=
1047 uintptr_t(code
->raw()) + code
->instructionsSize());
1048 CollectPerfSpewerJitCodeProfile(base
, size
, rangeName
.get());
1052 void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler
& masm
,
1054 if (!PerfEnabled()) {
1058 if (!opcodes_
.emplaceBack(masm
.currentOffset(), unsigned(op
))) {
1060 AutoLockPerfSpewer lock
;
1061 DisablePerfSpewer(lock
);
1065 void BaselineInterpreterPerfSpewer::recordOffset(MacroAssembler
& masm
,
1067 if (!PerfEnabled()) {
1071 UniqueChars desc
= DuplicateString(name
);
1072 if (!opcodes_
.emplaceBack(masm
.currentOffset(), desc
)) {
1074 AutoLockPerfSpewer lock
;
1075 DisablePerfSpewer(lock
);
1079 void IonPerfSpewer::saveProfile(JSContext
* cx
, JSScript
* script
,
1081 if (!PerfEnabled()) {
1084 UniqueChars desc
= GetFunctionDesc("Ion", cx
, script
);
1085 PerfSpewer::saveProfile(code
, desc
, script
);
1088 void js::jit::CollectPerfSpewerJitCodeProfile(JitCode
* code
, const char* msg
) {
1089 if (!code
|| !PerfEnabled()) {
1093 size_t size
= code
->instructionsSize();
1095 AutoLockPerfSpewer lock
;
1097 JS::JitCodeRecord
* profilerRecord
= CreateProfilerEntry(lock
);
1098 UniqueChars desc
= JS_smprintf("%s", msg
);
1099 PerfSpewer::CollectJitCodeInfo(desc
, code
, profilerRecord
, lock
);
1103 void js::jit::CollectPerfSpewerJitCodeProfile(uintptr_t base
, uint64_t size
,
1105 if (!PerfEnabled()) {
1110 AutoLockPerfSpewer lock
;
1112 JS::JitCodeRecord
* profilerRecord
= CreateProfilerEntry(lock
);
1113 UniqueChars desc
= JS_smprintf("%s", msg
);
1114 PerfSpewer::CollectJitCodeInfo(desc
, reinterpret_cast<void*>(base
), size
,
1115 profilerRecord
, lock
);
1119 void js::jit::CollectPerfSpewerWasmMap(uintptr_t base
, uintptr_t size
,
1120 const char* filename
,
1121 const char* annotation
) {
1122 if (size
== 0U || !PerfEnabled()) {
1125 AutoLockPerfSpewer lock
;
1127 JS::JitCodeRecord
* profilerRecord
= CreateProfilerEntry(lock
);
1128 UniqueChars desc
= JS_smprintf("%s: Function %s", filename
, annotation
);
1129 PerfSpewer::CollectJitCodeInfo(desc
, reinterpret_cast<void*>(base
),
1130 uint64_t(size
), profilerRecord
, lock
);
1133 void js::jit::CollectPerfSpewerWasmFunctionMap(uintptr_t base
, uintptr_t size
,
1134 const char* filename
,
1136 const char* funcName
) {
1137 if (size
== 0U || !PerfEnabled()) {
1140 AutoLockPerfSpewer lock
;
1142 JS::JitCodeRecord
* profilerRecord
= CreateProfilerEntry(lock
);
1144 JS_smprintf("%s:%u: Function %s", filename
, lineno
, funcName
);
1145 PerfSpewer::CollectJitCodeInfo(desc
, reinterpret_cast<void*>(base
),
1146 uint64_t(size
), profilerRecord
, lock
);
1149 void js::jit::PerfSpewerRangeRecorder::appendEntry(UniqueChars
& desc
) {
1150 if (!ranges
.append(std::make_pair(masm
.currentOffset(), std::move(desc
)))) {
1151 AutoLockPerfSpewer lock
;
1152 DisablePerfSpewer(lock
);
1157 void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name
) {
1158 if (!PerfEnabled()) {
1161 UniqueChars desc
= DuplicateString(name
);
1165 void js::jit::PerfSpewerRangeRecorder::recordVMWrapperOffset(const char* name
) {
1166 if (!PerfEnabled()) {
1170 UniqueChars desc
= JS_smprintf("VMWrapper: %s", name
);
1174 void js::jit::PerfSpewerRangeRecorder::recordOffset(const char* name
,
1177 if (!PerfEnabled()) {
1180 UniqueChars desc
= GetFunctionDesc(name
, cx
, script
);
1184 void js::jit::PerfSpewerRangeRecorder::collectRangesForJitCode(JitCode
* code
) {
1185 if (!PerfEnabled() || ranges
.empty()) {
1189 uintptr_t basePtr
= uintptr_t(code
->raw());
1190 uintptr_t offsetStart
= 0;
1192 for (OffsetPair
& pair
: ranges
) {
1193 uint32_t offsetEnd
= std::get
<0>(pair
);
1194 uintptr_t rangeSize
= uintptr_t(offsetEnd
- offsetStart
);
1195 const char* rangeName
= std::get
<1>(pair
).get();
1197 CollectPerfSpewerJitCodeProfile(basePtr
+ offsetStart
, rangeSize
,
1199 offsetStart
= offsetEnd
;
1202 MOZ_ASSERT(offsetStart
<= code
->instructionsSize());