blob: cdd7385b4ef83b3f4f0d9789cfb6d16a512f49c3 [file] [log] [blame]
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "SamplingProfiler.h"
#if ENABLE(SAMPLING_PROFILER)
#include "CallFrame.h"
#include "CodeBlock.h"
#include "Debugger.h"
#include "Executable.h"
#include "HeapInlines.h"
#include "HeapIterationScope.h"
#include "InlineCallFrame.h"
#include "Interpreter.h"
#include "JSCJSValueInlines.h"
#include "JSFunction.h"
#include "LLIntPCRanges.h"
#include "MarkedBlock.h"
#include "MarkedBlockSet.h"
#include "SlotVisitor.h"
#include "SlotVisitorInlines.h"
#include "VM.h"
#include "VMEntryScope.h"
namespace JSC {
static double sNumTotalStackTraces = 0;
static double sNumUnverifiedStackTraces = 0;
static double sNumTotalWalks = 0;
static double sNumFailedWalks = 0;
static const uint32_t sNumWalkReportingFrequency = 50;
static const double sWalkErrorPercentage = .05;
static const bool sReportStatsOnlyWhenTheyreAboveThreshold = false;
static const bool sReportStats = false;
using FrameType = SamplingProfiler::FrameType;
ALWAYS_INLINE static void reportStats()
{
if (sReportStats && sNumTotalWalks && static_cast<uint64_t>(sNumTotalWalks) % sNumWalkReportingFrequency == 0) {
if (!sReportStatsOnlyWhenTheyreAboveThreshold || (sNumFailedWalks / sNumTotalWalks > sWalkErrorPercentage)) {
dataLogF("Num total walks: %llu. Failed walks percent: %lf\n",
static_cast<uint64_t>(sNumTotalWalks), sNumFailedWalks / sNumTotalWalks);
dataLogF("Total stack traces: %llu. Needs verification percent: %lf\n",
static_cast<uint64_t>(sNumTotalStackTraces), sNumUnverifiedStackTraces / sNumTotalStackTraces);
}
}
}
class FrameWalker {
public:
FrameWalker(ExecState* callFrame, VM& vm, const LockHolder& codeBlockSetLocker, const LockHolder& machineThreadsLocker)
: m_vm(vm)
, m_callFrame(callFrame)
, m_vmEntryFrame(vm.topVMEntryFrame)
, m_codeBlockSetLocker(codeBlockSetLocker)
, m_machineThreadsLocker(machineThreadsLocker)
{
}
size_t walk(Vector<SamplingProfiler::StackFrame>& stackTrace, bool& didRunOutOfSpace, bool& stacktraceNeedsVerification)
{
stacktraceNeedsVerification = false;
if (sReportStats)
sNumTotalWalks++;
resetAtMachineFrame();
size_t maxStackTraceSize = stackTrace.size();
while (!isAtTop() && !m_bailingOut && m_depth < maxStackTraceSize) {
while (m_inlineCallFrame && m_depth < maxStackTraceSize) {
CodeBlock* codeBlock = m_inlineCallFrame->baselineCodeBlock.get();
RELEASE_ASSERT(isValidCodeBlock(codeBlock));
stackTrace[m_depth] = SamplingProfiler::StackFrame(SamplingProfiler::FrameType::VerifiedExecutable, codeBlock->ownerExecutable());
m_depth++;
m_inlineCallFrame = m_inlineCallFrame->directCaller.inlineCallFrame;
}
if (m_depth >= maxStackTraceSize)
break;
CodeBlock* codeBlock = m_callFrame->codeBlock();
if (isValidCodeBlock(codeBlock)) {
ExecutableBase* executable = codeBlock->ownerExecutable();
stackTrace[m_depth] = SamplingProfiler::StackFrame(FrameType::VerifiedExecutable,executable);
} else {
stacktraceNeedsVerification = true;
JSValue unsafeCallee = m_callFrame->unsafeCallee();
stackTrace[m_depth] = SamplingProfiler::StackFrame(FrameType::UnverifiedCallee, JSValue::encode(unsafeCallee));
}
m_depth++;
advanceToParentFrame();
resetAtMachineFrame();
}
didRunOutOfSpace = m_depth >= maxStackTraceSize && !isAtTop();
reportStats();
return m_depth;
}
bool wasValidWalk() const
{
return !m_bailingOut;
}
private:
void advanceToParentFrame()
{
m_callFrame = m_callFrame->callerFrame(m_vmEntryFrame);
}
bool isAtTop() const
{
return !m_callFrame;
}
void resetAtMachineFrame()
{
m_inlineCallFrame = nullptr;
if (isAtTop())
return;
if (!isValidFramePointer(m_callFrame)) {
// Guard against pausing the process at weird program points.
m_bailingOut = true;
if (sReportStats)
sNumFailedWalks++;
return;
}
#if ENABLE(DFG_JIT)
// If the frame doesn't have a code block, then it's not a
// DFG/FTL frame which means we're not an inlined frame.
CodeBlock* codeBlock = m_callFrame->codeBlock();
if (!codeBlock)
return;
if (!isValidCodeBlock(codeBlock)) {
m_bailingOut = true;
if (sReportStats)
sNumFailedWalks++;
return;
}
// If the code block does not have any code origins, then there's no
// inlining. Hence, we're not at an inlined frame.
if (!codeBlock->hasCodeOrigins())
return;
CallSiteIndex index = m_callFrame->callSiteIndex();
if (!codeBlock->canGetCodeOrigin(index)) {
// FIXME:
// For the most part, we only fail here when we're looking
// at the top most call frame. All other parent call frames
// should have set the CallSiteIndex when making a call.
//
// We should resort to getting information from the PC=>CodeOrigin mapping
// once we implement it: https://bugs.webkit.org/show_bug.cgi?id=152629
return;
}
m_inlineCallFrame = codeBlock->codeOrigin(index).inlineCallFrame;
#endif // !ENABLE(DFG_JIT)
}
bool isValidFramePointer(ExecState* exec)
{
uint8_t* fpCast = bitwise_cast<uint8_t*>(exec);
for (MachineThreads::Thread* thread = m_vm.heap.machineThreads().threadsListHead(m_machineThreadsLocker); thread; thread = thread->next) {
uint8_t* stackBase = static_cast<uint8_t*>(thread->stackBase);
uint8_t* stackLimit = static_cast<uint8_t*>(thread->stackEnd);
RELEASE_ASSERT(stackBase);
RELEASE_ASSERT(stackLimit);
if (fpCast <= stackBase && fpCast >= stackLimit)
return true;
}
return false;
}
bool isValidCodeBlock(CodeBlock* codeBlock)
{
if (!codeBlock)
return false;
bool result = m_vm.heap.codeBlockSet().contains(m_codeBlockSetLocker, codeBlock);
return result;
}
VM& m_vm;
ExecState* m_callFrame;
VMEntryFrame* m_vmEntryFrame;
const LockHolder& m_codeBlockSetLocker;
const LockHolder& m_machineThreadsLocker;
bool m_bailingOut { false };
InlineCallFrame* m_inlineCallFrame;
size_t m_depth { 0 };
};
SamplingProfiler::SamplingProfiler(VM& vm, RefPtr<Stopwatch>&& stopwatch)
: m_vm(vm)
, m_stopwatch(WTFMove(stopwatch))
, m_indexOfNextStackTraceToVerify(0)
, m_timingInterval(std::chrono::microseconds(1000))
, m_totalTime(0)
, m_timerQueue(WorkQueue::create("jsc.sampling-profiler.queue", WorkQueue::Type::Serial, WorkQueue::QOS::UserInteractive))
, m_jscExecutionThread(nullptr)
, m_isActive(false)
, m_isPaused(false)
, m_hasDispatchedFunction(false)
{
if (sReportStats) {
sNumTotalWalks = 0;
sNumFailedWalks = 0;
}
m_currentFrames.grow(256);
m_handler = [this] () {
LockHolder samplingProfilerLocker(m_lock);
if (!m_isActive || !m_jscExecutionThread || m_isPaused) {
m_hasDispatchedFunction = false;
deref();
return;
}
if (m_vm.entryScope) {
double nowTime = m_stopwatch->elapsedTime();
LockHolder machineThreadsLocker(m_vm.heap.machineThreads().getLock());
LockHolder codeBlockSetLocker(m_vm.heap.codeBlockSet().getLock());
LockHolder executableAllocatorLocker(m_vm.executableAllocator.getLock());
bool didSuspend = m_jscExecutionThread->suspend();
if (didSuspend) {
// While the JSC thread is suspended, we can't do things like malloc because the JSC thread
// may be holding the malloc lock.
ExecState* callFrame;
void* pc;
{
MachineThreads::Thread::Registers registers;
m_jscExecutionThread->getRegisters(registers);
callFrame = static_cast<ExecState*>(registers.framePointer());
pc = registers.instructionPointer();
m_jscExecutionThread->freeRegisters(registers);
}
// FIXME: Lets have a way of detecting when we're parsing code.
// https://bugs.webkit.org/show_bug.cgi?id=152761
if (m_vm.executableAllocator.isValidExecutableMemory(executableAllocatorLocker, pc)) {
if (m_vm.isExecutingInRegExpJIT) {
// FIXME: We're executing a regexp. Lets gather more intersting data.
// https://bugs.webkit.org/show_bug.cgi?id=152729
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.
}
} else if (LLInt::isLLIntPC(pc)) {
// We're okay to take a normal stack trace when the PC
// is in LLInt code.
} else {
// We resort to topCallFrame to see if we can get anything
// useful. We usually get here when we're executing C code.
callFrame = m_vm.topCallFrame;
}
size_t walkSize;
bool wasValidWalk;
bool didRunOutOfVectorSpace;
bool stacktraceNeedsVerification;
{
FrameWalker walker(callFrame, m_vm, codeBlockSetLocker, machineThreadsLocker);
walkSize = walker.walk(m_currentFrames, didRunOutOfVectorSpace, stacktraceNeedsVerification);
wasValidWalk = walker.wasValidWalk();
}
m_jscExecutionThread->resume();
// We can now use data structures that malloc, and do other interesting things, again.
// FIXME: It'd be interesting to take data about the program's state when
// we fail to take a stack trace: https://bugs.webkit.org/show_bug.cgi?id=152758
if (wasValidWalk && walkSize) {
if (sReportStats) {
sNumTotalStackTraces++;
if (stacktraceNeedsVerification)
sNumUnverifiedStackTraces++;
}
Vector<StackFrame> stackTrace;
stackTrace.reserveInitialCapacity(walkSize);
for (size_t i = 0; i < walkSize; i++) {
StackFrame frame = m_currentFrames[i];
stackTrace.uncheckedAppend(frame);
if (frame.frameType == FrameType::VerifiedExecutable)
m_seenExecutables.add(frame.u.verifiedExecutable);
}
m_stackTraces.append(StackTrace{ stacktraceNeedsVerification, nowTime, WTFMove(stackTrace) });
if (didRunOutOfVectorSpace)
m_currentFrames.grow(m_currentFrames.size() * 1.25);
m_totalTime += nowTime - m_lastTime;
}
}
}
m_lastTime = m_stopwatch->elapsedTime();
dispatchFunction(samplingProfilerLocker);
};
}
SamplingProfiler::~SamplingProfiler()
{
}
void SamplingProfiler::processUnverifiedStackTraces()
{
// This function needs to be called from the JSC execution thread.
RELEASE_ASSERT(m_lock.isLocked());
TinyBloomFilter filter = m_vm.heap.objectSpace().blocks().filter();
MarkedBlockSet& markedBlockSet = m_vm.heap.objectSpace().blocks();
for (unsigned i = m_indexOfNextStackTraceToVerify; i < m_stackTraces.size(); i++) {
StackTrace& stackTrace = m_stackTraces[i];
if (!stackTrace.needsVerification)
continue;
stackTrace.needsVerification = false;
for (StackFrame& stackFrame : stackTrace.frames) {
if (stackFrame.frameType != FrameType::UnverifiedCallee) {
RELEASE_ASSERT(stackFrame.frameType == FrameType::VerifiedExecutable);
continue;
}
JSValue callee = JSValue::decode(stackFrame.u.unverifiedCallee);
if (!Heap::isValueGCObject(filter, markedBlockSet, callee)) {
stackFrame.frameType = FrameType::Unknown;
continue;
}
JSCell* calleeCell = callee.asCell();
auto frameTypeFromCallData = [&] () -> FrameType {
FrameType result = FrameType::Unknown;
CallData callData;
CallType callType;
callType = getCallData(calleeCell, callData);
if (callType == CallTypeHost)
result = FrameType::Host;
return result;
};
if (calleeCell->type() != JSFunctionType) {
stackFrame.frameType = frameTypeFromCallData();
continue;
}
ExecutableBase* executable = static_cast<JSFunction*>(calleeCell)->executable();
if (!executable) {
stackFrame.frameType = frameTypeFromCallData();
continue;
}
RELEASE_ASSERT(Heap::isPointerGCObject(filter, markedBlockSet, executable));
stackFrame.frameType = FrameType::VerifiedExecutable;
stackFrame.u.verifiedExecutable = executable;
m_seenExecutables.add(executable);
}
}
m_indexOfNextStackTraceToVerify = m_stackTraces.size();
}
void SamplingProfiler::visit(SlotVisitor& slotVisitor)
{
RELEASE_ASSERT(m_lock.isLocked());
for (ExecutableBase* executable : m_seenExecutables)
slotVisitor.appendUnbarrieredReadOnlyPointer(executable);
}
void SamplingProfiler::shutdown()
{
stop();
}
void SamplingProfiler::start()
{
LockHolder locker(m_lock);
m_isActive = true;
dispatchIfNecessary(locker);
}
void SamplingProfiler::stop()
{
LockHolder locker(m_lock);
m_isActive = false;
reportStats();
}
void SamplingProfiler::pause()
{
LockHolder locker(m_lock);
m_isPaused = true;
reportStats();
}
void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread(const LockHolder&)
{
ASSERT(m_lock.isLocked());
m_jscExecutionThread = m_vm.heap.machineThreads().machineThreadForCurrentThread();
}
void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread()
{
LockHolder locker(m_lock);
noticeCurrentThreadAsJSCExecutionThread(locker);
}
void SamplingProfiler::dispatchIfNecessary(const LockHolder& locker)
{
if (m_isActive && !m_hasDispatchedFunction && m_jscExecutionThread && m_vm.entryScope) {
ref(); // Matching deref() is inside m_handler when m_handler stops recursing.
dispatchFunction(locker);
}
}
void SamplingProfiler::dispatchFunction(const LockHolder&)
{
m_hasDispatchedFunction = true;
m_isPaused = false;
m_lastTime = m_stopwatch->elapsedTime();
m_timerQueue->dispatchAfter(m_timingInterval, m_handler);
}
void SamplingProfiler::noticeJSLockAcquisition()
{
LockHolder locker(m_lock);
noticeCurrentThreadAsJSCExecutionThread(locker);
}
void SamplingProfiler::noticeVMEntry()
{
LockHolder locker(m_lock);
ASSERT(m_vm.entryScope);
noticeCurrentThreadAsJSCExecutionThread(locker);
m_lastTime = m_stopwatch->elapsedTime();
dispatchIfNecessary(locker);
}
void SamplingProfiler::clearData()
{
LockHolder locker(m_lock);
m_stackTraces.clear();
m_seenExecutables.clear();
m_indexOfNextStackTraceToVerify = 0;
}
static String displayName(const SamplingProfiler::StackFrame& stackFrame)
{
if (stackFrame.frameType == FrameType::Unknown)
return ASCIILiteral("<unknown>");
if (stackFrame.frameType == FrameType::Host)
return ASCIILiteral("<host>");
RELEASE_ASSERT(stackFrame.frameType != FrameType::UnverifiedCallee);
ExecutableBase* executable = stackFrame.u.verifiedExecutable;
if (executable->isHostFunction())
return ASCIILiteral("<host>");
if (executable->isFunctionExecutable()) {
String result = static_cast<FunctionExecutable*>(executable)->inferredName().string();
if (!result.isEmpty())
return result;
return ASCIILiteral("<anonymous-function>");
}
if (executable->isEvalExecutable())
return ASCIILiteral("<eval>");
if (executable->isProgramExecutable())
return ASCIILiteral("<global>");
if (executable->isModuleProgramExecutable())
return ASCIILiteral("<module>");
RELEASE_ASSERT_NOT_REACHED();
return "";
}
String SamplingProfiler::stacktracesAsJSON()
{
m_lock.lock();
{
HeapIterationScope heapIterationScope(m_vm.heap);
processUnverifiedStackTraces();
}
StringBuilder json;
json.appendLiteral("[");
bool loopedOnce = false;
auto comma = [&] {
if (loopedOnce)
json.appendLiteral(",");
};
for (const StackTrace& stackTrace : m_stackTraces) {
comma();
json.appendLiteral("[");
loopedOnce = false;
for (const StackFrame& stackFrame : stackTrace.frames) {
comma();
json.appendLiteral("\"");
json.append(displayName(stackFrame));
json.appendLiteral("\"");
loopedOnce = true;
}
json.appendLiteral("]");
loopedOnce = true;
}
json.appendLiteral("]");
m_lock.unlock();
return json.toString();
}
} // namespace JSC
namespace WTF {
using namespace JSC;
void printInternal(PrintStream& out, SamplingProfiler::FrameType frameType)
{
switch (frameType) {
case SamplingProfiler::FrameType::VerifiedExecutable:
out.print("VerifiedExecutable");
break;
case SamplingProfiler::FrameType::UnverifiedCallee:
out.print("UnverifiedCallee");
break;
case SamplingProfiler::FrameType::Host:
out.print("Host");
break;
case SamplingProfiler::FrameType::Unknown:
out.print("Unknown");
break;
}
}
} // namespace WTF
#endif // ENABLE(SAMPLING_PROFILER)