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