summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp')
-rw-r--r--gfx/skia/skia/src/sksl/tracing/SkVMDebugTracePlayer.cpp284
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