blob: 09fcf385da391dc21a744d4c0c83892f3888037e [file] [log] [blame]
/*
* 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/SkVMDebugTracePlayer.h"
#include <limits.h>
#include <algorithm>
#include <type_traits>
#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 SkVMTraceInfo& trace : fDebugTrace->fTraceInfo) {
if (trace.op == SkVMTraceInfo::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::SkVMSlotInfo& 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 SkVMTraceInfo& trace = fDebugTrace->fTraceInfo[position];
switch (trace.op) {
case SkVMTraceInfo::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 SkVMTraceInfo::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 SkVMTraceInfo::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 SkVMTraceInfo::Op::kExit: { // data: function index, (unused)
SkASSERT(!fStack.empty());
SkASSERT(fStack.back().fFunction == trace.data[0]);
fStack.pop_back();
return true;
}
case SkVMTraceInfo::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