1/*
2 * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "SamplingProfiler.h"
28
29#if ENABLE(SAMPLING_PROFILER)
30
31#include "CallFrame.h"
32#include "CatchScope.h"
33#include "CodeBlock.h"
34#include "CodeBlockSet.h"
35#include "HeapIterationScope.h"
36#include "HeapUtil.h"
37#include "InlineCallFrame.h"
38#include "Interpreter.h"
39#include "JSCInlines.h"
40#include "JSFunction.h"
41#include "LLIntPCRanges.h"
42#include "MachineContext.h"
43#include "MarkedBlock.h"
44#include "MarkedBlockSet.h"
45#include "MarkedSpaceInlines.h"
46#include "NativeExecutable.h"
47#include "PCToCodeOriginMap.h"
48#include "SlotVisitor.h"
49#include "StrongInlines.h"
50#include "VM.h"
51#include <thread>
52#include <wtf/FilePrintStream.h>
53#include <wtf/HashSet.h>
54#include <wtf/RefPtr.h>
55#include <wtf/StackTrace.h>
56#include <wtf/text/StringBuilder.h>
57#include <wtf/text/StringConcatenateNumbers.h>
58
59namespace JSC {
60
61static double sNumTotalStackTraces = 0;
62static double sNumTotalWalks = 0;
63static double sNumFailedWalks = 0;
64static const uint32_t sNumWalkReportingFrequency = 50;
65static const double sWalkErrorPercentage = .05;
66static const bool sReportStatsOnlyWhenTheyreAboveThreshold = false;
67static const bool sReportStats = false;
68
69using FrameType = SamplingProfiler::FrameType;
70using UnprocessedStackFrame = SamplingProfiler::UnprocessedStackFrame;
71
72ALWAYS_INLINE static void reportStats()
73{
74 if (sReportStats && sNumTotalWalks && static_cast<uint64_t>(sNumTotalWalks) % sNumWalkReportingFrequency == 0) {
75 if (!sReportStatsOnlyWhenTheyreAboveThreshold || (sNumFailedWalks / sNumTotalWalks > sWalkErrorPercentage)) {
76 dataLogF("Num total walks: %llu. Failed walks percent: %lf\n",
77 static_cast<unsigned long long>(sNumTotalWalks), sNumFailedWalks / sNumTotalWalks);
78 }
79 }
80}
81
82class FrameWalker {
83public:
84 FrameWalker(VM& vm, ExecState* callFrame, const AbstractLocker& codeBlockSetLocker, const AbstractLocker& machineThreadsLocker)
85 : m_vm(vm)
86 , m_callFrame(callFrame)
87 , m_entryFrame(vm.topEntryFrame)
88 , m_codeBlockSetLocker(codeBlockSetLocker)
89 , m_machineThreadsLocker(machineThreadsLocker)
90 {
91 }
92
93 SUPPRESS_ASAN
94 size_t walk(Vector<UnprocessedStackFrame>& stackTrace, bool& didRunOutOfSpace)
95 {
96 if (sReportStats)
97 sNumTotalWalks++;
98 resetAtMachineFrame();
99 size_t maxStackTraceSize = stackTrace.size();
100 while (!isAtTop() && !m_bailingOut && m_depth < maxStackTraceSize) {
101 recordJSFrame(stackTrace);
102 advanceToParentFrame();
103 resetAtMachineFrame();
104 }
105 didRunOutOfSpace = m_depth >= maxStackTraceSize && !isAtTop();
106 reportStats();
107 return m_depth;
108 }
109
110 bool wasValidWalk() const
111 {
112 return !m_bailingOut;
113 }
114
115protected:
116
117 SUPPRESS_ASAN
118 void recordJSFrame(Vector<UnprocessedStackFrame>& stackTrace)
119 {
120 CallSiteIndex callSiteIndex;
121 CalleeBits unsafeCallee = m_callFrame->unsafeCallee();
122 CodeBlock* codeBlock = m_callFrame->unsafeCodeBlock();
123 if (codeBlock) {
124 ASSERT(isValidCodeBlock(codeBlock));
125 callSiteIndex = m_callFrame->unsafeCallSiteIndex();
126 }
127 stackTrace[m_depth] = UnprocessedStackFrame(codeBlock, unsafeCallee, callSiteIndex);
128 m_depth++;
129 }
130
131 SUPPRESS_ASAN
132 void advanceToParentFrame()
133 {
134 m_callFrame = m_callFrame->unsafeCallerFrame(m_entryFrame);
135 }
136
137 bool isAtTop() const
138 {
139 return !m_callFrame;
140 }
141
142 SUPPRESS_ASAN
143 void resetAtMachineFrame()
144 {
145 if (isAtTop())
146 return;
147
148 if (!isValidFramePointer(m_callFrame)) {
149 // Guard against pausing the process at weird program points.
150 m_bailingOut = true;
151 if (sReportStats)
152 sNumFailedWalks++;
153 return;
154 }
155
156 CodeBlock* codeBlock = m_callFrame->unsafeCodeBlock();
157 if (!codeBlock)
158 return;
159
160 if (!isValidCodeBlock(codeBlock)) {
161 m_bailingOut = true;
162 if (sReportStats)
163 sNumFailedWalks++;
164 return;
165 }
166 }
167
168 bool isValidFramePointer(void* exec)
169 {
170 uint8_t* fpCast = bitwise_cast<uint8_t*>(exec);
171 for (auto& thread : m_vm.heap.machineThreads().threads(m_machineThreadsLocker)) {
172 uint8_t* stackBase = static_cast<uint8_t*>(thread->stack().origin());
173 uint8_t* stackLimit = static_cast<uint8_t*>(thread->stack().end());
174 RELEASE_ASSERT(stackBase);
175 RELEASE_ASSERT(stackLimit);
176 RELEASE_ASSERT(stackLimit <= stackBase);
177 if (fpCast < stackBase && fpCast >= stackLimit)
178 return true;
179 }
180 return false;
181 }
182
183 bool isValidCodeBlock(CodeBlock* codeBlock)
184 {
185 if (!codeBlock)
186 return false;
187 bool result = m_vm.heap.codeBlockSet().contains(m_codeBlockSetLocker, codeBlock);
188 return result;
189 }
190
191 VM& m_vm;
192 ExecState* m_callFrame;
193 EntryFrame* m_entryFrame;
194 const AbstractLocker& m_codeBlockSetLocker;
195 const AbstractLocker& m_machineThreadsLocker;
196 bool m_bailingOut { false };
197 size_t m_depth { 0 };
198};
199
200class CFrameWalker : public FrameWalker {
201public:
202 typedef FrameWalker Base;
203
204 CFrameWalker(VM& vm, void* machineFrame, ExecState* callFrame, const AbstractLocker& codeBlockSetLocker, const AbstractLocker& machineThreadsLocker)
205 : Base(vm, callFrame, codeBlockSetLocker, machineThreadsLocker)
206 , m_machineFrame(machineFrame)
207 {
208 }
209
210 size_t walk(Vector<UnprocessedStackFrame>& stackTrace, bool& didRunOutOfSpace)
211 {
212 if (sReportStats)
213 sNumTotalWalks++;
214 resetAtMachineFrame();
215 size_t maxStackTraceSize = stackTrace.size();
216 // The way the C walker decides if a frame it is about to trace is C or JS is by
217 // ensuring m_callFrame points to some frame above the machineFrame.
218 if (!isAtTop() && !m_bailingOut && m_machineFrame == m_callFrame) {
219 recordJSFrame(stackTrace);
220 Base::advanceToParentFrame();
221 resetAtMachineFrame();
222 }
223
224 while (!isAtTop() && !m_bailingOut && m_depth < maxStackTraceSize) {
225 if (m_machineFrame >= m_callFrame) {
226 // If we get to this state we probably have an invalid trace.
227 m_bailingOut = true;
228 break;
229 }
230
231 if (isCFrame()) {
232 RELEASE_ASSERT(!LLInt::isLLIntPC(frame()->callerFrame));
233 stackTrace[m_depth] = UnprocessedStackFrame(frame()->returnPC);
234 m_depth++;
235 } else
236 recordJSFrame(stackTrace);
237 advanceToParentFrame();
238 resetAtMachineFrame();
239 }
240 didRunOutOfSpace = m_depth >= maxStackTraceSize && !isAtTop();
241 reportStats();
242 return m_depth;
243 }
244
245private:
246
247 bool isCFrame()
248 {
249 return frame()->callerFrame != m_callFrame;
250 }
251
252 void advanceToParentFrame()
253 {
254 if (!isCFrame())
255 Base::advanceToParentFrame();
256 m_machineFrame = frame()->callerFrame;
257 }
258
259 void resetAtMachineFrame()
260 {
261 if (!isValidFramePointer(m_machineFrame)) {
262 // Guard against pausing the process at weird program points.
263 m_bailingOut = true;
264 if (sReportStats)
265 sNumFailedWalks++;
266 return;
267 }
268 Base::resetAtMachineFrame();
269 }
270
271 CallerFrameAndPC* frame()
272 {
273 return reinterpret_cast<CallerFrameAndPC*>(m_machineFrame);
274 }
275
276 void* m_machineFrame;
277};
278
279SamplingProfiler::SamplingProfiler(VM& vm, RefPtr<Stopwatch>&& stopwatch)
280 : m_isPaused(false)
281 , m_isShutDown(false)
282 , m_vm(vm)
283 , m_weakRandom()
284 , m_stopwatch(WTFMove(stopwatch))
285 , m_timingInterval(Seconds::fromMicroseconds(Options::sampleInterval()))
286{
287 if (sReportStats) {
288 sNumTotalWalks = 0;
289 sNumFailedWalks = 0;
290 }
291
292 m_currentFrames.grow(256);
293}
294
295SamplingProfiler::~SamplingProfiler()
296{
297}
298
299void SamplingProfiler::createThreadIfNecessary(const AbstractLocker&)
300{
301 ASSERT(m_lock.isLocked());
302
303 if (m_thread)
304 return;
305
306 RefPtr<SamplingProfiler> profiler = this;
307 m_thread = Thread::create("jsc.sampling-profiler.thread", [profiler] {
308 profiler->timerLoop();
309 });
310}
311
312void SamplingProfiler::timerLoop()
313{
314 while (true) {
315 Seconds stackTraceProcessingTime = 0_s;
316 {
317 LockHolder locker(m_lock);
318 if (UNLIKELY(m_isShutDown))
319 return;
320
321 if (!m_isPaused && m_jscExecutionThread)
322 takeSample(locker, stackTraceProcessingTime);
323
324 m_lastTime = m_stopwatch->elapsedTime();
325 }
326
327 // Read section 6.2 of this paper for more elaboration of why we add a random
328 // fluctuation here. The main idea is to prevent our timer from being in sync
329 // with some system process such as a scheduled context switch.
330 // http://plv.colorado.edu/papers/mytkowicz-pldi10.pdf
331 double randomSignedNumber = (m_weakRandom.get() * 2.0) - 1.0; // A random number between [-1, 1).
332 Seconds randomFluctuation = m_timingInterval * 0.2 * randomSignedNumber;
333 WTF::sleep(m_timingInterval - std::min(m_timingInterval, stackTraceProcessingTime) + randomFluctuation);
334 }
335}
336
337void SamplingProfiler::takeSample(const AbstractLocker&, Seconds& stackTraceProcessingTime)
338{
339 ASSERT(m_lock.isLocked());
340 if (m_vm.entryScope) {
341 Seconds nowTime = m_stopwatch->elapsedTime();
342
343 auto machineThreadsLocker = holdLock(m_vm.heap.machineThreads().getLock());
344 LockHolder codeBlockSetLocker(m_vm.heap.codeBlockSet().getLock());
345 LockHolder executableAllocatorLocker(ExecutableAllocator::singleton().getLock());
346
347 auto didSuspend = m_jscExecutionThread->suspend();
348 if (didSuspend) {
349 // While the JSC thread is suspended, we can't do things like malloc because the JSC thread
350 // may be holding the malloc lock.
351 void* machineFrame;
352 ExecState* callFrame;
353 void* machinePC;
354 bool topFrameIsLLInt = false;
355 void* llintPC;
356 {
357 PlatformRegisters registers;
358 m_jscExecutionThread->getRegisters(registers);
359 machineFrame = MachineContext::framePointer(registers);
360 callFrame = static_cast<ExecState*>(machineFrame);
361 auto instructionPointer = MachineContext::instructionPointer(registers);
362 if (instructionPointer)
363 machinePC = instructionPointer->untaggedExecutableAddress();
364 else
365 machinePC = nullptr;
366 llintPC = removeCodePtrTag(MachineContext::llintInstructionPointer(registers));
367 assertIsNotTagged(machinePC);
368 }
369 // FIXME: Lets have a way of detecting when we're parsing code.
370 // https://bugs.webkit.org/show_bug.cgi?id=152761
371 if (ExecutableAllocator::singleton().isValidExecutableMemory(executableAllocatorLocker, machinePC)) {
372 if (m_vm.isExecutingInRegExpJIT) {
373 // FIXME: We're executing a regexp. Lets gather more intersting data.
374 // https://bugs.webkit.org/show_bug.cgi?id=152729
375 callFrame = m_vm.topCallFrame; // We need to do this or else we'd fail our backtrace validation b/c this isn't a JS frame.
376 }
377 } else if (LLInt::isLLIntPC(machinePC)) {
378 topFrameIsLLInt = true;
379 // We're okay to take a normal stack trace when the PC
380 // is in LLInt code.
381 } else {
382 // We resort to topCallFrame to see if we can get anything
383 // useful. We usually get here when we're executing C code.
384 callFrame = m_vm.topCallFrame;
385 }
386
387 size_t walkSize;
388 bool wasValidWalk;
389 bool didRunOutOfVectorSpace;
390 if (Options::sampleCCode()) {
391 CFrameWalker walker(m_vm, machineFrame, callFrame, codeBlockSetLocker, machineThreadsLocker);
392 walkSize = walker.walk(m_currentFrames, didRunOutOfVectorSpace);
393 wasValidWalk = walker.wasValidWalk();
394 } else {
395 FrameWalker walker(m_vm, callFrame, codeBlockSetLocker, machineThreadsLocker);
396 walkSize = walker.walk(m_currentFrames, didRunOutOfVectorSpace);
397 wasValidWalk = walker.wasValidWalk();
398 }
399
400 m_jscExecutionThread->resume();
401
402 auto startTime = MonotonicTime::now();
403 // We can now use data structures that malloc, and do other interesting things, again.
404
405 // FIXME: It'd be interesting to take data about the program's state when
406 // we fail to take a stack trace: https://bugs.webkit.org/show_bug.cgi?id=152758
407 if (wasValidWalk && walkSize) {
408 if (sReportStats)
409 sNumTotalStackTraces++;
410 Vector<UnprocessedStackFrame> stackTrace;
411 stackTrace.reserveInitialCapacity(walkSize);
412 for (size_t i = 0; i < walkSize; i++) {
413 UnprocessedStackFrame frame = m_currentFrames[i];
414 stackTrace.uncheckedAppend(frame);
415 }
416
417 m_unprocessedStackTraces.append(UnprocessedStackTrace { nowTime, machinePC, topFrameIsLLInt, llintPC, WTFMove(stackTrace) });
418
419 if (didRunOutOfVectorSpace)
420 m_currentFrames.grow(m_currentFrames.size() * 1.25);
421 }
422
423 auto endTime = MonotonicTime::now();
424 stackTraceProcessingTime = endTime - startTime;
425 }
426 }
427}
428
429static ALWAYS_INLINE unsigned tryGetBytecodeIndex(unsigned llintPC, CodeBlock* codeBlock, bool& isValid)
430{
431#if ENABLE(DFG_JIT)
432 RELEASE_ASSERT(!codeBlock->hasCodeOrigins());
433#endif
434
435#if USE(JSVALUE64)
436 unsigned bytecodeIndex = llintPC;
437 if (bytecodeIndex < codeBlock->instructionsSize()) {
438 isValid = true;
439 return bytecodeIndex;
440 }
441 isValid = false;
442 return 0;
443#else
444 Instruction* instruction = bitwise_cast<Instruction*>(llintPC);
445
446 if (codeBlock->instructions().contains(instruction)) {
447 isValid = true;
448 return codeBlock->bytecodeOffset(instruction);
449 }
450 isValid = false;
451 return 0;
452#endif
453}
454
455void SamplingProfiler::processUnverifiedStackTraces()
456{
457 // This function needs to be called from the JSC execution thread.
458 RELEASE_ASSERT(m_lock.isLocked());
459
460 TinyBloomFilter filter = m_vm.heap.objectSpace().blocks().filter();
461
462 for (UnprocessedStackTrace& unprocessedStackTrace : m_unprocessedStackTraces) {
463 m_stackTraces.append(StackTrace());
464 StackTrace& stackTrace = m_stackTraces.last();
465 stackTrace.timestamp = unprocessedStackTrace.timestamp;
466
467 auto populateCodeLocation = [] (CodeBlock* codeBlock, unsigned bytecodeIndex, StackFrame::CodeLocation& location) {
468 if (bytecodeIndex < codeBlock->instructionsSize()) {
469 int divot;
470 int startOffset;
471 int endOffset;
472 codeBlock->expressionRangeForBytecodeOffset(bytecodeIndex, divot, startOffset, endOffset,
473 location.lineNumber, location.columnNumber);
474 location.bytecodeIndex = bytecodeIndex;
475 }
476 if (Options::collectSamplingProfilerDataForJSCShell()) {
477 location.codeBlockHash = codeBlock->hash();
478 location.jitType = codeBlock->jitType();
479 }
480 };
481
482 auto appendCodeBlock = [&] (CodeBlock* codeBlock, unsigned bytecodeIndex) {
483 stackTrace.frames.append(StackFrame(codeBlock->ownerExecutable()));
484 m_liveCellPointers.add(codeBlock->ownerExecutable());
485 populateCodeLocation(codeBlock, bytecodeIndex, stackTrace.frames.last().semanticLocation);
486 };
487
488 auto appendEmptyFrame = [&] {
489 stackTrace.frames.append(StackFrame());
490 };
491
492 auto storeCalleeIntoLastFrame = [&] (CalleeBits calleeBits) {
493 // Set the callee if it's a valid GC object.
494 StackFrame& stackFrame = stackTrace.frames.last();
495 bool alreadyHasExecutable = !!stackFrame.executable;
496 if (calleeBits.isWasm()) {
497 stackFrame.frameType = FrameType::Unknown;
498 return;
499 }
500
501 JSValue callee = calleeBits.asCell();
502 if (!HeapUtil::isValueGCObject(m_vm.heap, filter, callee)) {
503 if (!alreadyHasExecutable)
504 stackFrame.frameType = FrameType::Unknown;
505 return;
506 }
507
508 JSCell* calleeCell = callee.asCell();
509 auto setFallbackFrameType = [&] {
510 ASSERT(!alreadyHasExecutable);
511 FrameType result = FrameType::Unknown;
512 CallData callData;
513 CallType callType;
514 callType = getCallData(m_vm, calleeCell, callData);
515 if (callType == CallType::Host)
516 result = FrameType::Host;
517
518 stackFrame.frameType = result;
519 };
520
521 auto addCallee = [&] (JSObject* callee) {
522 stackFrame.callee = callee;
523 m_liveCellPointers.add(callee);
524 };
525
526 if (calleeCell->type() != JSFunctionType) {
527 if (JSObject* object = jsDynamicCast<JSObject*>(*calleeCell->vm(), calleeCell))
528 addCallee(object);
529
530 if (!alreadyHasExecutable)
531 setFallbackFrameType();
532
533 return;
534 }
535
536 addCallee(jsCast<JSFunction*>(calleeCell));
537
538 if (alreadyHasExecutable)
539 return;
540
541 ExecutableBase* executable = jsCast<JSFunction*>(calleeCell)->executable();
542 if (!executable) {
543 setFallbackFrameType();
544 return;
545 }
546
547 RELEASE_ASSERT(HeapUtil::isPointerGCObjectJSCell(m_vm.heap, filter, executable));
548 stackFrame.frameType = FrameType::Executable;
549 stackFrame.executable = executable;
550 m_liveCellPointers.add(executable);
551 };
552
553 auto appendCodeOrigin = [&] (CodeBlock* machineCodeBlock, CodeOrigin origin) {
554 size_t startIndex = stackTrace.frames.size(); // We want to change stack traces that we're about to append.
555
556 CodeOrigin machineOrigin;
557 origin.walkUpInlineStack([&] (const CodeOrigin& codeOrigin) {
558 machineOrigin = codeOrigin;
559 auto* inlineCallFrame = codeOrigin.inlineCallFrame();
560 appendCodeBlock(inlineCallFrame ? inlineCallFrame->baselineCodeBlock.get() : machineCodeBlock, codeOrigin.bytecodeIndex());
561 });
562
563 if (Options::collectSamplingProfilerDataForJSCShell()) {
564 RELEASE_ASSERT(machineOrigin.isSet());
565 RELEASE_ASSERT(!machineOrigin.inlineCallFrame());
566
567 StackFrame::CodeLocation machineLocation = stackTrace.frames.last().semanticLocation;
568
569 // We want to tell each inlined frame about the machine frame
570 // they were inlined into. Currently, we only use this for dumping
571 // output on the command line, but we could extend it to the web
572 // inspector in the future if we find a need for it there.
573 RELEASE_ASSERT(stackTrace.frames.size());
574 m_liveCellPointers.add(machineCodeBlock);
575 for (size_t i = startIndex; i < stackTrace.frames.size() - 1; i++)
576 stackTrace.frames[i].machineLocation = std::make_pair(machineLocation, machineCodeBlock);
577 }
578 };
579
580 // Prepend the top-most inlined frame if needed and gather
581 // location information about where the top frame is executing.
582 size_t startIndex = 0;
583 if (unprocessedStackTrace.frames.size() && !!unprocessedStackTrace.frames[0].verifiedCodeBlock) {
584 CodeBlock* topCodeBlock = unprocessedStackTrace.frames[0].verifiedCodeBlock;
585 if (unprocessedStackTrace.topFrameIsLLInt) {
586 // We reuse LLInt CodeBlocks for the baseline JIT, so we need to check for both jit types.
587 // This might also be false for various reasons (known and unknown), even though
588 // it's super unlikely. One reason that this can be false is when we throw from a DFG frame,
589 // and we end up having to unwind past an EntryFrame, we will end up executing
590 // inside the LLInt's handleUncaughtException. So we just protect against this
591 // by ignoring it.
592 unsigned bytecodeIndex = 0;
593 if (topCodeBlock->jitType() == JITType::InterpreterThunk || topCodeBlock->jitType() == JITType::BaselineJIT) {
594 bool isValidPC;
595 unsigned bits;
596#if USE(JSVALUE64)
597 bits = static_cast<unsigned>(bitwise_cast<uintptr_t>(unprocessedStackTrace.llintPC));
598#else
599 bits = bitwise_cast<unsigned>(unprocessedStackTrace.llintPC);
600#endif
601 bytecodeIndex = tryGetBytecodeIndex(bits, topCodeBlock, isValidPC);
602
603 UNUSED_PARAM(isValidPC); // FIXME: do something with this info for the web inspector: https://bugs.webkit.org/show_bug.cgi?id=153455
604
605 appendCodeBlock(topCodeBlock, bytecodeIndex);
606 storeCalleeIntoLastFrame(unprocessedStackTrace.frames[0].unverifiedCallee);
607 startIndex = 1;
608 }
609 } else {
610#if ENABLE(JIT)
611 if (Optional<CodeOrigin> codeOrigin = topCodeBlock->findPC(unprocessedStackTrace.topPC)) {
612 appendCodeOrigin(topCodeBlock, *codeOrigin);
613 storeCalleeIntoLastFrame(unprocessedStackTrace.frames[0].unverifiedCallee);
614 startIndex = 1;
615 }
616#endif
617 UNUSED_PARAM(appendCodeOrigin);
618 }
619 }
620
621 for (size_t i = startIndex; i < unprocessedStackTrace.frames.size(); i++) {
622 UnprocessedStackFrame& unprocessedStackFrame = unprocessedStackTrace.frames[i];
623 if (CodeBlock* codeBlock = unprocessedStackFrame.verifiedCodeBlock) {
624 CallSiteIndex callSiteIndex = unprocessedStackFrame.callSiteIndex;
625
626 auto appendCodeBlockNoInlining = [&] {
627 bool isValidPC;
628 appendCodeBlock(codeBlock, tryGetBytecodeIndex(callSiteIndex.bits(), codeBlock, isValidPC));
629 };
630
631#if ENABLE(DFG_JIT)
632 if (codeBlock->hasCodeOrigins()) {
633 if (codeBlock->canGetCodeOrigin(callSiteIndex))
634 appendCodeOrigin(codeBlock, codeBlock->codeOrigin(callSiteIndex));
635 else
636 appendCodeBlock(codeBlock, std::numeric_limits<unsigned>::max());
637 } else
638 appendCodeBlockNoInlining();
639#else
640 appendCodeBlockNoInlining();
641#endif
642 } else if (unprocessedStackFrame.cCodePC) {
643 appendEmptyFrame();
644 stackTrace.frames.last().cCodePC = unprocessedStackFrame.cCodePC;
645 stackTrace.frames.last().frameType = FrameType::C;
646 } else
647 appendEmptyFrame();
648
649 // Note that this is okay to do if we walked the inline stack because
650 // the machine frame will be at the top of the processed stack trace.
651 if (!unprocessedStackFrame.cCodePC)
652 storeCalleeIntoLastFrame(unprocessedStackFrame.unverifiedCallee);
653 }
654 }
655
656 m_unprocessedStackTraces.clear();
657}
658
659void SamplingProfiler::visit(SlotVisitor& slotVisitor)
660{
661 RELEASE_ASSERT(m_lock.isLocked());
662 for (JSCell* cell : m_liveCellPointers)
663 slotVisitor.appendUnbarriered(cell);
664}
665
666void SamplingProfiler::shutdown()
667{
668 LockHolder locker(m_lock);
669 m_isShutDown = true;
670}
671
672void SamplingProfiler::start()
673{
674 LockHolder locker(m_lock);
675 start(locker);
676}
677
678void SamplingProfiler::start(const AbstractLocker& locker)
679{
680 ASSERT(m_lock.isLocked());
681 m_isPaused = false;
682 createThreadIfNecessary(locker);
683}
684
685void SamplingProfiler::pause(const AbstractLocker&)
686{
687 ASSERT(m_lock.isLocked());
688 m_isPaused = true;
689 reportStats();
690}
691
692void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread(const AbstractLocker&)
693{
694 ASSERT(m_lock.isLocked());
695 m_jscExecutionThread = &Thread::current();
696}
697
698void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread()
699{
700 LockHolder locker(m_lock);
701 noticeCurrentThreadAsJSCExecutionThread(locker);
702}
703
704void SamplingProfiler::noticeJSLockAcquisition()
705{
706 LockHolder locker(m_lock);
707 noticeCurrentThreadAsJSCExecutionThread(locker);
708}
709
710void SamplingProfiler::noticeVMEntry()
711{
712 LockHolder locker(m_lock);
713 ASSERT(m_vm.entryScope);
714 noticeCurrentThreadAsJSCExecutionThread(locker);
715 m_lastTime = m_stopwatch->elapsedTime();
716 createThreadIfNecessary(locker);
717}
718
719void SamplingProfiler::clearData(const AbstractLocker&)
720{
721 ASSERT(m_lock.isLocked());
722 m_stackTraces.clear();
723 m_liveCellPointers.clear();
724 m_unprocessedStackTraces.clear();
725}
726
727String SamplingProfiler::StackFrame::nameFromCallee(VM& vm)
728{
729 if (!callee)
730 return String();
731
732 auto scope = DECLARE_CATCH_SCOPE(vm);
733 ExecState* exec = callee->globalObject(vm)->globalExec();
734 auto getPropertyIfPureOperation = [&] (const Identifier& ident) -> String {
735 PropertySlot slot(callee, PropertySlot::InternalMethodType::VMInquiry);
736 PropertyName propertyName(ident);
737 bool hasProperty = callee->getPropertySlot(exec, propertyName, slot);
738 scope.assertNoException();
739 if (hasProperty) {
740 if (slot.isValue()) {
741 JSValue nameValue = slot.getValue(exec, propertyName);
742 if (isJSString(nameValue))
743 return asString(nameValue)->tryGetValue();
744 }
745 }
746 return String();
747 };
748
749 String name = getPropertyIfPureOperation(vm.propertyNames->displayName);
750 if (!name.isEmpty())
751 return name;
752
753 return getPropertyIfPureOperation(vm.propertyNames->name);
754}
755
756String SamplingProfiler::StackFrame::displayName(VM& vm)
757{
758 {
759 String name = nameFromCallee(vm);
760 if (!name.isEmpty())
761 return name;
762 }
763
764 if (frameType == FrameType::Unknown || frameType == FrameType::C) {
765#if HAVE(DLADDR)
766 if (frameType == FrameType::C) {
767 auto demangled = WTF::StackTrace::demangle(const_cast<void*>(cCodePC));
768 if (demangled)
769 return String(demangled->demangledName() ? demangled->demangledName() : demangled->mangledName());
770 WTF::dataLog("couldn't get a name");
771 }
772#endif
773 return "(unknown)"_s;
774 }
775 if (frameType == FrameType::Host)
776 return "(host)"_s;
777
778 if (executable->isHostFunction())
779 return static_cast<NativeExecutable*>(executable)->name();
780
781 if (executable->isFunctionExecutable())
782 return static_cast<FunctionExecutable*>(executable)->ecmaName().string();
783 if (executable->isProgramExecutable() || executable->isEvalExecutable())
784 return "(program)"_s;
785 if (executable->isModuleProgramExecutable())
786 return "(module)"_s;
787
788 RELEASE_ASSERT_NOT_REACHED();
789 return String();
790}
791
792String SamplingProfiler::StackFrame::displayNameForJSONTests(VM& vm)
793{
794 {
795 String name = nameFromCallee(vm);
796 if (!name.isEmpty())
797 return name;
798 }
799
800 if (frameType == FrameType::Unknown || frameType == FrameType::C)
801 return "(unknown)"_s;
802 if (frameType == FrameType::Host)
803 return "(host)"_s;
804
805 if (executable->isHostFunction())
806 return static_cast<NativeExecutable*>(executable)->name();
807
808 if (executable->isFunctionExecutable()) {
809 String result = static_cast<FunctionExecutable*>(executable)->ecmaName().string();
810 if (result.isEmpty())
811 return "(anonymous function)"_s;
812 return result;
813 }
814 if (executable->isEvalExecutable())
815 return "(eval)"_s;
816 if (executable->isProgramExecutable())
817 return "(program)"_s;
818 if (executable->isModuleProgramExecutable())
819 return "(module)"_s;
820
821 RELEASE_ASSERT_NOT_REACHED();
822 return String();
823}
824
825int SamplingProfiler::StackFrame::functionStartLine()
826{
827 if (frameType == FrameType::Unknown || frameType == FrameType::Host || frameType == FrameType::C)
828 return -1;
829
830 if (executable->isHostFunction())
831 return -1;
832 return static_cast<ScriptExecutable*>(executable)->firstLine();
833}
834
835unsigned SamplingProfiler::StackFrame::functionStartColumn()
836{
837 if (frameType == FrameType::Unknown || frameType == FrameType::Host || frameType == FrameType::C)
838 return std::numeric_limits<unsigned>::max();
839
840 if (executable->isHostFunction())
841 return std::numeric_limits<unsigned>::max();
842
843 return static_cast<ScriptExecutable*>(executable)->startColumn();
844}
845
846intptr_t SamplingProfiler::StackFrame::sourceID()
847{
848 if (frameType == FrameType::Unknown || frameType == FrameType::Host || frameType == FrameType::C)
849 return -1;
850
851 if (executable->isHostFunction())
852 return -1;
853
854 return static_cast<ScriptExecutable*>(executable)->sourceID();
855}
856
857String SamplingProfiler::StackFrame::url()
858{
859 if (frameType == FrameType::Unknown || frameType == FrameType::Host || frameType == FrameType::C)
860 return emptyString();
861
862 if (executable->isHostFunction())
863 return emptyString();
864
865 String url = static_cast<ScriptExecutable*>(executable)->sourceURL();
866 if (url.isEmpty())
867 return static_cast<ScriptExecutable*>(executable)->source().provider()->sourceURLDirective(); // Fall back to sourceURL directive.
868 return url;
869}
870
871Vector<SamplingProfiler::StackTrace> SamplingProfiler::releaseStackTraces(const AbstractLocker& locker)
872{
873 ASSERT(m_lock.isLocked());
874 {
875 HeapIterationScope heapIterationScope(m_vm.heap);
876 processUnverifiedStackTraces();
877 }
878
879 Vector<StackTrace> result(WTFMove(m_stackTraces));
880 clearData(locker);
881 return result;
882}
883
884String SamplingProfiler::stackTracesAsJSON()
885{
886 DeferGC deferGC(m_vm.heap);
887 LockHolder locker(m_lock);
888
889 {
890 HeapIterationScope heapIterationScope(m_vm.heap);
891 processUnverifiedStackTraces();
892 }
893
894 StringBuilder json;
895 json.append('[');
896
897 bool loopedOnce = false;
898 auto comma = [&] {
899 if (loopedOnce)
900 json.append(',');
901 };
902 for (StackTrace& stackTrace : m_stackTraces) {
903 comma();
904 json.append('[');
905 loopedOnce = false;
906 for (StackFrame& stackFrame : stackTrace.frames) {
907 comma();
908 json.appendQuotedJSONString(stackFrame.displayNameForJSONTests(m_vm));
909 loopedOnce = true;
910 }
911 json.append(']');
912 loopedOnce = true;
913 }
914
915 json.append(']');
916
917 clearData(locker);
918
919 return json.toString();
920}
921
922void SamplingProfiler::registerForReportAtExit()
923{
924 static Lock registrationLock;
925 static HashSet<RefPtr<SamplingProfiler>>* profilesToReport;
926
927 LockHolder holder(registrationLock);
928
929 if (!profilesToReport) {
930 profilesToReport = new HashSet<RefPtr<SamplingProfiler>>();
931 atexit([]() {
932 for (auto profile : *profilesToReport)
933 profile->reportDataToOptionFile();
934 });
935 }
936
937 profilesToReport->add(adoptRef(this));
938 m_needsReportAtExit = true;
939}
940
941void SamplingProfiler::reportDataToOptionFile()
942{
943 if (m_needsReportAtExit) {
944 m_needsReportAtExit = false;
945 const char* path = Options::samplingProfilerPath();
946 StringPrintStream pathOut;
947 pathOut.print(path, "/");
948 pathOut.print("JSCSampilingProfile-", reinterpret_cast<uintptr_t>(this), ".txt");
949 auto out = FilePrintStream::open(pathOut.toCString().data(), "w");
950 reportTopFunctions(*out);
951 reportTopBytecodes(*out);
952 }
953}
954
955void SamplingProfiler::reportTopFunctions()
956{
957 reportTopFunctions(WTF::dataFile());
958}
959
960void SamplingProfiler::reportTopFunctions(PrintStream& out)
961{
962 LockHolder locker(m_lock);
963 DeferGCForAWhile deferGC(m_vm.heap);
964
965 {
966 HeapIterationScope heapIterationScope(m_vm.heap);
967 processUnverifiedStackTraces();
968 }
969
970
971 HashMap<String, size_t> functionCounts;
972 for (StackTrace& stackTrace : m_stackTraces) {
973 if (!stackTrace.frames.size())
974 continue;
975
976 StackFrame& frame = stackTrace.frames.first();
977 String frameDescription = makeString(frame.displayName(m_vm), ':', frame.sourceID());
978 functionCounts.add(frameDescription, 0).iterator->value++;
979 }
980
981 auto takeMax = [&] () -> std::pair<String, size_t> {
982 String maxFrameDescription;
983 size_t maxFrameCount = 0;
984 for (auto entry : functionCounts) {
985 if (entry.value > maxFrameCount) {
986 maxFrameCount = entry.value;
987 maxFrameDescription = entry.key;
988 }
989 }
990 if (!maxFrameDescription.isEmpty())
991 functionCounts.remove(maxFrameDescription);
992 return std::make_pair(maxFrameDescription, maxFrameCount);
993 };
994
995 if (Options::samplingProfilerTopFunctionsCount()) {
996 out.print("\n\nSampling rate: ", m_timingInterval.microseconds(), " microseconds\n");
997 out.print("Top functions as <numSamples 'functionName:sourceID'>\n");
998 for (size_t i = 0; i < Options::samplingProfilerTopFunctionsCount(); i++) {
999 auto pair = takeMax();
1000 if (pair.first.isEmpty())
1001 break;
1002 out.printf("%6zu ", pair.second);
1003 out.print(" '", pair.first, "'\n");
1004 }
1005 }
1006}
1007
1008void SamplingProfiler::reportTopBytecodes()
1009{
1010 reportTopBytecodes(WTF::dataFile());
1011}
1012
1013void SamplingProfiler::reportTopBytecodes(PrintStream& out)
1014{
1015 LockHolder locker(m_lock);
1016 DeferGCForAWhile deferGC(m_vm.heap);
1017
1018 {
1019 HeapIterationScope heapIterationScope(m_vm.heap);
1020 processUnverifiedStackTraces();
1021 }
1022
1023 HashMap<String, size_t> bytecodeCounts;
1024 for (StackTrace& stackTrace : m_stackTraces) {
1025 if (!stackTrace.frames.size())
1026 continue;
1027
1028 auto descriptionForLocation = [&] (StackFrame::CodeLocation location) -> String {
1029 String bytecodeIndex;
1030 String codeBlockHash;
1031 if (location.hasBytecodeIndex())
1032 bytecodeIndex = String::number(location.bytecodeIndex);
1033 else
1034 bytecodeIndex = "<nil>";
1035
1036 if (location.hasCodeBlockHash()) {
1037 StringPrintStream stream;
1038 location.codeBlockHash.dump(stream);
1039 codeBlockHash = stream.toString();
1040 } else
1041 codeBlockHash = "<nil>";
1042
1043 return makeString("#", codeBlockHash, ":", JITCode::typeName(location.jitType), ":", bytecodeIndex);
1044 };
1045
1046 StackFrame& frame = stackTrace.frames.first();
1047 String frameDescription = makeString(frame.displayName(m_vm), descriptionForLocation(frame.semanticLocation));
1048 if (Optional<std::pair<StackFrame::CodeLocation, CodeBlock*>> machineLocation = frame.machineLocation) {
1049 frameDescription = makeString(frameDescription, " <-- ",
1050 machineLocation->second->inferredName().data(), descriptionForLocation(machineLocation->first));
1051 }
1052 bytecodeCounts.add(frameDescription, 0).iterator->value++;
1053 }
1054
1055 auto takeMax = [&] () -> std::pair<String, size_t> {
1056 String maxFrameDescription;
1057 size_t maxFrameCount = 0;
1058 for (auto entry : bytecodeCounts) {
1059 if (entry.value > maxFrameCount) {
1060 maxFrameCount = entry.value;
1061 maxFrameDescription = entry.key;
1062 }
1063 }
1064 if (!maxFrameDescription.isEmpty())
1065 bytecodeCounts.remove(maxFrameDescription);
1066 return std::make_pair(maxFrameDescription, maxFrameCount);
1067 };
1068
1069 if (Options::samplingProfilerTopBytecodesCount()) {
1070 out.print("\n\nSampling rate: ", m_timingInterval.microseconds(), " microseconds\n");
1071 out.print("Hottest bytecodes as <numSamples 'functionName#hash:JITType:bytecodeIndex'>\n");
1072 for (size_t i = 0; i < Options::samplingProfilerTopBytecodesCount(); i++) {
1073 auto pair = takeMax();
1074 if (pair.first.isEmpty())
1075 break;
1076 out.printf("%6zu ", pair.second);
1077 out.print(" '", pair.first, "'\n");
1078 }
1079 }
1080}
1081
1082#if OS(DARWIN)
1083mach_port_t SamplingProfiler::machThread()
1084{
1085 if (!m_thread)
1086 return MACH_PORT_NULL;
1087
1088 return m_thread->machThread();
1089}
1090#endif
1091
1092} // namespace JSC
1093
1094namespace WTF {
1095
1096using namespace JSC;
1097
1098void printInternal(PrintStream& out, SamplingProfiler::FrameType frameType)
1099{
1100 switch (frameType) {
1101 case SamplingProfiler::FrameType::Executable:
1102 out.print("Executable");
1103 break;
1104 case SamplingProfiler::FrameType::Host:
1105 out.print("Host");
1106 break;
1107 case SamplingProfiler::FrameType::C:
1108 case SamplingProfiler::FrameType::Unknown:
1109 out.print("Unknown");
1110 break;
1111 }
1112}
1113
1114} // namespace WTF
1115
1116#endif // ENABLE(SAMPLING_PROFILER)
1117