diff options
Diffstat (limited to 'gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp')
-rw-r--r-- | gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp new file mode 100644 index 0000000000..7ae9e4638b --- /dev/null +++ b/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp @@ -0,0 +1,284 @@ +/* + * 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/SkSLDebugInfo.h" +#include "src/sksl/tracing/SkVMDebugTracePlayer.h" + +#include <limits.h> +#include <algorithm> +#include <utility> + +namespace SkSL { + +void SkVMDebugTracePlayer::reset(sk_sp<SkVMDebugTrace> 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 SkVMDebugTracePlayer::step() { + this->tidyState(); + while (!this->traceHasCompleted()) { + if (this->execute(fCursor++)) { + break; + } + } +} + +void SkVMDebugTracePlayer::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 SkVMDebugTracePlayer::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 SkVMDebugTracePlayer::run() { + this->tidyState(); + while (!this->traceHasCompleted()) { + if (this->execute(fCursor++)) { + if (this->atBreakpoint()) { + break; + } + } + } +} + +void SkVMDebugTracePlayer::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 SkVMDebugTracePlayer::traceHasCompleted() const { + return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size(); +} + +int32_t SkVMDebugTracePlayer::getCurrentLine() const { + SkASSERT(!fStack.empty()); + return fStack.back().fLine; +} + +int32_t SkVMDebugTracePlayer::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 SkVMDebugTracePlayer::atBreakpoint() const { + return fBreakpointLines.count(this->getCurrentLine()); +} + +void SkVMDebugTracePlayer::setBreakpoints(std::unordered_set<int> breakpointLines) { + fBreakpointLines = std::move(breakpointLines); +} + +void SkVMDebugTracePlayer::addBreakpoint(int line) { + fBreakpointLines.insert(line); +} + +void SkVMDebugTracePlayer::removeBreakpoint(int line) { + fBreakpointLines.erase(line); +} + +std::vector<int> SkVMDebugTracePlayer::getCallStack() const { + SkASSERT(!fStack.empty()); + std::vector<int> funcs; + funcs.reserve(fStack.size() - 1); + for (size_t index = 1; index < fStack.size(); ++index) { + funcs.push_back(fStack[index].fFunction); + } + return funcs; +} + +int SkVMDebugTracePlayer::getStackDepth() const { + SkASSERT(!fStack.empty()); + return fStack.size() - 1; +} + +std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getVariablesForDisplayMask( + const SkBitSet& displayMask) const { + SkASSERT(displayMask.size() == fSlots.size()); + + std::vector<VariableData> 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<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::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<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getGlobalVariables() const { + if (fStack.empty()) { + return {}; + } + return this->getVariablesForDisplayMask(fStack.front().fDisplayMask); +} + +void SkVMDebugTracePlayer::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 SkVMDebugTracePlayer::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.size() > 0); + 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 |