/* * Copyright 2021 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/sksl/tracing/SkSLDebugTracePlayer.h" #include "src/sksl/tracing/SkSLDebugTracePriv.h" #include #include #include namespace SkSL { void SkSLDebugTracePlayer::reset(sk_sp debugTrace) { size_t nslots = debugTrace ? debugTrace->fSlotInfo.size() : 0; fDebugTrace = debugTrace; fCursor = 0; fScope = 0; fSlots.clear(); fSlots.resize(nslots, {/*fValue=*/0, /*fScope=*/INT_MAX, /*fWriteTime=*/0}); fStack.clear(); fStack.push_back({/*fFunction=*/-1, /*fLine=*/-1, /*fDisplayMask=*/SkBitSet(nslots)}); fDirtyMask.emplace(nslots); fReturnValues.emplace(nslots); if (fDebugTrace) { for (size_t slotIdx = 0; slotIdx < nslots; ++slotIdx) { if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue >= 0) { fReturnValues->set(slotIdx); } } for (const TraceInfo& trace : fDebugTrace->fTraceInfo) { if (trace.op == TraceInfo::Op::kLine) { fLineNumbers[trace.data[0]] += 1; } } } } void SkSLDebugTracePlayer::step() { this->tidyState(); while (!this->traceHasCompleted()) { if (this->execute(fCursor++)) { break; } } } void SkSLDebugTracePlayer::stepOver() { this->tidyState(); size_t initialStackDepth = fStack.size(); while (!this->traceHasCompleted()) { bool canEscapeFromThisStackDepth = (fStack.size() <= initialStackDepth); if (this->execute(fCursor++)) { if (canEscapeFromThisStackDepth || this->atBreakpoint()) { break; } } } } void SkSLDebugTracePlayer::stepOut() { this->tidyState(); size_t initialStackDepth = fStack.size(); while (!this->traceHasCompleted()) { if (this->execute(fCursor++)) { bool hasEscapedFromInitialStackDepth = (fStack.size() < initialStackDepth); if (hasEscapedFromInitialStackDepth || this->atBreakpoint()) { break; } } } } void SkSLDebugTracePlayer::run() { this->tidyState(); while (!this->traceHasCompleted()) { if (this->execute(fCursor++)) { if (this->atBreakpoint()) { break; } } } } void SkSLDebugTracePlayer::tidyState() { fDirtyMask->reset(); // Conceptually this is `fStack.back().fDisplayMask &= ~fReturnValues`, but SkBitSet doesn't // support masking one set of bits against another. fReturnValues->forEachSetIndex([&](int slot) { fStack.back().fDisplayMask.reset(slot); }); } bool SkSLDebugTracePlayer::traceHasCompleted() const { return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size(); } int32_t SkSLDebugTracePlayer::getCurrentLine() const { SkASSERT(!fStack.empty()); return fStack.back().fLine; } int32_t SkSLDebugTracePlayer::getCurrentLineInStackFrame(int stackFrameIndex) const { // The first entry on the stack is the "global" frame before we enter main, so offset our index // by one to account for it. ++stackFrameIndex; SkASSERT(stackFrameIndex > 0); SkASSERT((size_t)stackFrameIndex < fStack.size()); return fStack[stackFrameIndex].fLine; } bool SkSLDebugTracePlayer::atBreakpoint() const { return fBreakpointLines.count(this->getCurrentLine()); } void SkSLDebugTracePlayer::setBreakpoints(std::unordered_set breakpointLines) { fBreakpointLines = std::move(breakpointLines); } void SkSLDebugTracePlayer::addBreakpoint(int line) { fBreakpointLines.insert(line); } void SkSLDebugTracePlayer::removeBreakpoint(int line) { fBreakpointLines.erase(line); } std::vector SkSLDebugTracePlayer::getCallStack() const { SkASSERT(!fStack.empty()); std::vector funcs; funcs.reserve(fStack.size() - 1); for (size_t index = 1; index < fStack.size(); ++index) { funcs.push_back(fStack[index].fFunction); } return funcs; } int SkSLDebugTracePlayer::getStackDepth() const { SkASSERT(!fStack.empty()); return fStack.size() - 1; } std::vector SkSLDebugTracePlayer::getVariablesForDisplayMask( const SkBitSet& displayMask) const { SkASSERT(displayMask.size() == fSlots.size()); std::vector vars; displayMask.forEachSetIndex([&](int slot) { double typedValue = fDebugTrace->interpretValueBits(slot, fSlots[slot].fValue); vars.push_back({slot, fDirtyMask->test(slot), typedValue}); }); // Order the variable list so that the most recently-written variables are shown at the top. std::stable_sort(vars.begin(), vars.end(), [&](const VariableData& a, const VariableData& b) { return fSlots[a.fSlotIndex].fWriteTime > fSlots[b.fSlotIndex].fWriteTime; }); return vars; } std::vector SkSLDebugTracePlayer::getLocalVariables( int stackFrameIndex) const { // The first entry on the stack is the "global" frame before we enter main, so offset our index // by one to account for it. ++stackFrameIndex; if (stackFrameIndex <= 0 || (size_t)stackFrameIndex >= fStack.size()) { SkDEBUGFAILF("stack frame %d doesn't exist", stackFrameIndex - 1); return {}; } return this->getVariablesForDisplayMask(fStack[stackFrameIndex].fDisplayMask); } std::vector SkSLDebugTracePlayer::getGlobalVariables() const { if (fStack.empty()) { return {}; } return this->getVariablesForDisplayMask(fStack.front().fDisplayMask); } void SkSLDebugTracePlayer::updateVariableWriteTime(int slotIdx, size_t cursor) { // The slotIdx could point to any slot within a variable. // We want to update the write time on EVERY slot associated with this variable. // The SlotInfo's groupIndex gives us enough information to find the affected range. const SkSL::SlotDebugInfo& changedSlot = fDebugTrace->fSlotInfo[slotIdx]; slotIdx -= changedSlot.groupIndex; SkASSERT(slotIdx >= 0); SkASSERT(slotIdx < (int)fDebugTrace->fSlotInfo.size()); for (;;) { fSlots[slotIdx++].fWriteTime = cursor; // Stop if we've reached the final slot. if (slotIdx >= (int)fDebugTrace->fSlotInfo.size()) { break; } // Each separate variable-group starts with a groupIndex of 0; stop when we detect this. if (fDebugTrace->fSlotInfo[slotIdx].groupIndex == 0) { break; } } } bool SkSLDebugTracePlayer::execute(size_t position) { if (position >= fDebugTrace->fTraceInfo.size()) { SkDEBUGFAILF("position %zu out of range", position); return true; } const TraceInfo& trace = fDebugTrace->fTraceInfo[position]; switch (trace.op) { case TraceInfo::Op::kLine: { // data: line number, (unused) SkASSERT(!fStack.empty()); int lineNumber = trace.data[0]; SkASSERT(lineNumber >= 0); SkASSERT((size_t)lineNumber < fDebugTrace->fSource.size()); SkASSERT(fLineNumbers[lineNumber] > 0); fStack.back().fLine = lineNumber; fLineNumbers[lineNumber] -= 1; return true; } case TraceInfo::Op::kVar: { // data: slot, value int slotIdx = trace.data[0]; int value = trace.data[1]; SkASSERT(slotIdx >= 0); SkASSERT((size_t)slotIdx < fDebugTrace->fSlotInfo.size()); fSlots[slotIdx].fValue = value; fSlots[slotIdx].fScope = std::min<>(fSlots[slotIdx].fScope, fScope); this->updateVariableWriteTime(slotIdx, position); if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue < 0) { // Normal variables are associated with the current function. SkASSERT(!fStack.empty()); fStack.rbegin()[0].fDisplayMask.set(slotIdx); } else { // Return values are associated with the parent function (since the current function // is exiting and we won't see them there). SkASSERT(fStack.size() > 1); fStack.rbegin()[1].fDisplayMask.set(slotIdx); } fDirtyMask->set(slotIdx); break; } case TraceInfo::Op::kEnter: { // data: function index, (unused) int fnIdx = trace.data[0]; SkASSERT(fnIdx >= 0); SkASSERT((size_t)fnIdx < fDebugTrace->fFuncInfo.size()); fStack.push_back({/*fFunction=*/fnIdx, /*fLine=*/-1, /*fDisplayMask=*/SkBitSet(fDebugTrace->fSlotInfo.size())}); break; } case TraceInfo::Op::kExit: { // data: function index, (unused) SkASSERT(!fStack.empty()); SkASSERT(fStack.back().fFunction == trace.data[0]); fStack.pop_back(); return true; } case TraceInfo::Op::kScope: { // data: scope delta, (unused) SkASSERT(!fStack.empty()); fScope += trace.data[0]; if (trace.data[0] < 0) { // If the scope is being reduced, discard variables that are now out of scope. for (size_t slotIdx = 0; slotIdx < fSlots.size(); ++slotIdx) { if (fScope < fSlots[slotIdx].fScope) { fSlots[slotIdx].fScope = INT_MAX; fStack.back().fDisplayMask.reset(slotIdx); } } } return false; } } return false; } } // namespace SkSL