Create SkVMDebugTrace player class.

This class is responsible for interpreting a debug trace and allowing it
to be stepped through, like a debugger. It tracks the current line
number, call stack, slot values, and associates slots with stack frames.
It supports stepping forward or stepping over (i.e., stepping to the
next line in the current function, hiding function calls).

Change-Id: I2b7d90c3b38b0006bebdfbf65a7bf678d5227d56
Bug: skia:12666
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/482460
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
This commit is contained in:
John Stiles 2021-12-10 14:45:22 -05:00 committed by SkCQ
parent c279e7640b
commit f3b4617828
5 changed files with 666 additions and 0 deletions

View File

@ -201,6 +201,8 @@ skia_sksl_sources = [
"$_src/sksl/ir/SkSLVariableReference.cpp",
"$_src/sksl/ir/SkSLVariableReference.h",
"$_src/sksl/spirv.h",
"$_src/sksl/tracing/SkVMDebugTracePlayer.cpp",
"$_src/sksl/tracing/SkVMDebugTracePlayer.h",
"$_src/sksl/transform/SkSLBuiltinVariableScanner.cpp",
"$_src/sksl/transform/SkSLEliminateDeadFunctions.cpp",
"$_src/sksl/transform/SkSLEliminateDeadGlobalVariables.cpp",

View File

@ -260,6 +260,7 @@ tests_sources = [
"$_tests/SkTBlockListTest.cpp",
"$_tests/SkTOptionalTest.cpp",
"$_tests/SkUTFTest.cpp",
"$_tests/SkVMDebugTracePlayerTest.cpp",
"$_tests/SkVMDebugTraceTest.cpp",
"$_tests/SkVMTest.cpp",
"$_tests/SkVxTest.cpp",

View File

@ -0,0 +1,150 @@
/*
* 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"
namespace SkSL {
void SkVMDebugTracePlayer::reset(sk_sp<SkVMDebugTrace> debugTrace) {
size_t nslots = debugTrace ? debugTrace->fSlotInfo.size() : 0;
fDebugTrace = debugTrace;
fCursor = 0;
fSlots.clear();
fSlots.resize(nslots);
fStack.clear();
fStack.push_back({/*fFunction=*/-1,
/*fLine=*/-1,
/*fDisplayMask=*/SkBitSet(nslots)});
}
void SkVMDebugTracePlayer::step() {
while (!this->traceHasCompleted()) {
if (this->execute(fCursor++)) {
break;
}
}
}
void SkVMDebugTracePlayer::stepOver() {
size_t initialStackDepth = fStack.size();
while (!this->traceHasCompleted()) {
bool canEscapeFromThisStackDepth = (fStack.size() <= initialStackDepth);
if (this->execute(fCursor++) && canEscapeFromThisStackDepth) {
break;
}
}
}
bool SkVMDebugTracePlayer::traceHasCompleted() const {
return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size();
}
int32_t SkVMDebugTracePlayer::getCurrentLine() const {
SkASSERT(!fStack.empty());
return fStack.back().fLine;
}
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& bits) const {
SkASSERT(bits.size() == fSlots.size());
std::vector<VariableData> vars;
bits.forEachSetIndex([&](int slot) {
vars.push_back({slot, fSlots[slot]});
});
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);
}
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());
fStack.back().fLine = lineNumber;
return true;
}
case SkVMTraceInfo::Op::kVar: { // data: slot, value
int slot = trace.data[0];
int value = trace.data[1];
SkASSERT(slot >= 0);
SkASSERT((size_t)slot < fDebugTrace->fSlotInfo.size());
fSlots[slot] = value;
if (fDebugTrace->fSlotInfo[slot].fnReturnValue < 0) {
// Normal variables are associated with the current function.
SkASSERT(fStack.size() > 0);
fStack.rbegin()[0].fDisplayMask.set(slot);
} 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(slot);
}
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;
}
}
return false;
}
} // namespace SkSL

View File

@ -0,0 +1,71 @@
/*
* 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/codegen/SkVMDebugTrace.h"
#include "src/utils/SkBitSet.h"
namespace SkSL {
/**
* Plays back a SkVM debug trace, allowing its contents to be viewed like a traditional debugger.
*/
class SkVMDebugTracePlayer {
public:
/** Resets playback. */
void reset(sk_sp<SkVMDebugTrace> trace);
/** Advances the simulation to the next Line op. */
void step();
/** Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs. */
void stepOver();
/** Returns true if we have reached the end of the trace. */
bool traceHasCompleted() const;
/** Retrieves the cursor position. */
size_t cursor() { return fCursor; }
/** Retrieves the current line. */
int32_t getCurrentLine() const;
/** Returns the call stack as an array of FunctionInfo indices. */
std::vector<int> getCallStack() const;
/** Returns the size of the call stack. */
int getStackDepth() const;
/** Returns variables from a stack frame, or from global scope. */
struct VariableData {
int fSlotIndex;
int32_t fValue; // caller must type-pun bits to float/bool based on slot type
};
std::vector<VariableData> getLocalVariables(int stackFrameIndex) const;
std::vector<VariableData> getGlobalVariables() const;
private:
/**
* Executes the trace op at the passed-in cursor position. Returns true if we've reached a line
* or exit trace op, which indicate a stopping point.
*/
bool execute(size_t position);
/** Returns a vector of the indices and values of each slot that is enabled in `bits`. */
std::vector<VariableData> getVariablesForDisplayMask(const SkBitSet& bits) const;
struct StackFrame {
int32_t fFunction; // from fFuncInfo
int32_t fLine; // our current line number within the function
SkBitSet fDisplayMask; // the variable slots which have been touched in this function
};
sk_sp<SkVMDebugTrace> fDebugTrace;
size_t fCursor = 0; // position of the read head
std::vector<int32_t> fSlots; // values in each slot
std::vector<StackFrame> fStack; // the execution stack
};
} // namespace SkSL

View File

@ -0,0 +1,442 @@
/*
* 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 "include/core/SkM44.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/codegen/SkSLVMCodeGenerator.h"
#include "src/sksl/codegen/SkVMDebugTrace.h"
#include "src/sksl/tracing/SkVMDebugTracePlayer.h"
#include "tests/Test.h"
static sk_sp<SkSL::SkVMDebugTrace> make_trace(skiatest::Reporter* r, SkSL::String src) {
SkSL::ShaderCaps caps;
SkSL::Compiler compiler(&caps);
SkSL::Program::Settings settings;
settings.fOptimize = false;
skvm::Builder b;
std::unique_ptr<SkSL::Program> program = compiler.convertProgram(SkSL::ProgramKind::kGeneric,
src, settings);
REPORTER_ASSERT(r, program);
const SkSL::FunctionDefinition* main = SkSL::Program_GetFunction(*program, "main");
auto debugTrace = sk_make_sp<SkSL::SkVMDebugTrace>();
SkSL::ProgramToSkVM(*program, *main, &b, debugTrace.get(), /*uniforms=*/{});
skvm::Program p = b.done();
REPORTER_ASSERT(r, p.nargs() == 1);
int result;
p.eval(1, &result);
return debugTrace;
}
static std::string make_stack_string(const SkSL::SkVMDebugTrace& trace,
const SkSL::SkVMDebugTracePlayer& player) {
std::vector<int> callStack = player.getCallStack();
std::string text;
const char* separator = "";
for (int frame : callStack) {
text += separator;
separator = " -> ";
if (frame >= 0 && (size_t)frame < trace.fFuncInfo.size()) {
text += trace.fFuncInfo[frame].name;
} else {
text += "???";
}
}
return text;
}
static std::string make_vars_string(
const SkSL::SkVMDebugTrace& trace,
const std::vector<SkSL::SkVMDebugTracePlayer::VariableData>& vars) {
std::string text;
const char* separator = "";
for (const SkSL::SkVMDebugTracePlayer::VariableData& var : vars) {
text += separator;
separator = ", ";
if (var.fSlotIndex < 0 || (size_t)var.fSlotIndex >= trace.fSlotInfo.size()) {
text += "???";
continue;
}
const SkSL::SkVMSlotInfo& slot = trace.fSlotInfo[var.fSlotIndex];
text += slot.name;
text += ":";
text += std::to_string(slot.componentIndex);
text += " = ";
switch (slot.numberKind) {
default:
text += "???";
break;
case SkSL::Type::NumberKind::kSigned:
text += std::to_string(var.fValue);
break;
case SkSL::Type::NumberKind::kBoolean:
text += var.fValue ? "true" : "false";
break;
case SkSL::Type::NumberKind::kUnsigned: {
uint32_t uintValue;
memcpy(&uintValue, &var.fValue, sizeof(uint32_t));
text += std::to_string(uintValue);
break;
}
case SkSL::Type::NumberKind::kFloat: {
float floatValue;
memcpy(&floatValue, &var.fValue, sizeof(float));
text += std::to_string(floatValue);
break;
}
}
}
return text;
}
static std::string make_local_vars_string(const SkSL::SkVMDebugTrace& trace,
const SkSL::SkVMDebugTracePlayer& player,
int frame = -1) {
if (frame == -1) {
frame = player.getStackDepth() - 1;
}
return make_vars_string(trace, player.getLocalVariables(frame));
}
static std::string make_global_vars_string(const SkSL::SkVMDebugTrace& trace,
const SkSL::SkVMDebugTracePlayer& player) {
return make_vars_string(trace, player.getGlobalVariables());
}
DEF_TEST(SkSLTracePlayerHelloWorld, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
int main() { // Line 2
return 2 + 2; // Line 3
} // Line 4
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
// We have not started tracing yet.
REPORTER_ASSERT(r, player.cursor() == 0);
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
player.step();
// We should now be inside main.
REPORTER_ASSERT(r, player.cursor() > 0);
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCurrentLine() == 3);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
REPORTER_ASSERT(r, player.getLocalVariables(0).empty());
player.step();
// We have now completed the trace.
REPORTER_ASSERT(r, player.cursor() > 0);
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 4");
}
DEF_TEST(SkSLTracePlayerReset, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
int main() { // Line 2
return 2 + 2; // Line 3
} // Line 4
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
// We have not started tracing yet.
REPORTER_ASSERT(r, player.cursor() == 0);
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
player.step();
// We should now be inside main.
REPORTER_ASSERT(r, player.cursor() > 0);
REPORTER_ASSERT(r, player.getCurrentLine() == 3);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
REPORTER_ASSERT(r, player.getLocalVariables(0).empty());
player.reset(trace);
// We should be back to square one.
REPORTER_ASSERT(r, player.cursor() == 0);
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
}
DEF_TEST(SkSLTracePlayerFunctions, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
int fnB() { // Line 2
return 2 + 2; // Line 3
} // Line 4
int fnA() { // Line 5
return fnB(); // Line 6
} // Line 7
int main() { // Line 8
return fnA(); // Line 9
} // Line 10
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
// We have not started tracing yet.
REPORTER_ASSERT(r, player.cursor() == 0);
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
player.step();
// We should now be inside main.
REPORTER_ASSERT(r, !player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCurrentLine() == 9);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, player.getGlobalVariables().empty());
REPORTER_ASSERT(r, player.getLocalVariables(0).empty());
player.stepOver();
// We should now have completed execution.
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, player.getCurrentLine() == -1);
REPORTER_ASSERT(r, player.getCallStack().empty());
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 4");
// Watch the stack grow and shrink as single-step.
player.reset(trace);
player.step();
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main() -> int fnA()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main() -> int fnA() -> int fnB()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main() -> int fnA()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "[fnB].result:0 = 4");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "[fnA].result:0 = 4");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 4");
}
DEF_TEST(SkSLTracePlayerVariables, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
void func() { // Line 2
int z = 456; // Line 3
return; // Line 4
} // Line 5
int main() { // Line 6
int a = 123; // Line 7
bool b = true; // Line 8
func(); // Line 9
float4 c = float4(0, 0.5, 1, -1); // Line 10
float3x3 d = float3x3(2); // Line 11
return a; // Line 12
} // Line 13
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 7);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 8);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "a:0 = 123");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 9);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "a:0 = 123, b:0 = true");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 3);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main() -> void func()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 4);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main() -> void func()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "z:0 = 456");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 9);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "a:0 = 123, b:0 = true");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 10);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "a:0 = 123, b:0 = true");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 11);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) ==
"a:0 = 123, b:0 = true, c:0 = 0.000000, c:1 = 0.500000, "
"c:2 = 1.000000, c:3 = -1.000000");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 12);
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "int main()");
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) ==
"a:0 = 123, b:0 = true, c:0 = 0.000000, c:1 = 0.500000, c:2 = "
"1.000000, c:3 = -1.000000, d:0 = 2.000000, d:1 = 0.000000, d:2 = "
"0.000000, d:3 = 0.000000, d:4 = 2.000000, d:5 = 0.000000, d:6 = "
"0.000000, d:7 = 0.000000, d:8 = 2.000000");
player.step();
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, make_stack_string(*trace, player) == "");
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 123");
}
DEF_TEST(SkSLTracePlayerIfStatement, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
int main() { // Line 2
int val; // Line 3
if (true) { // Line 4
val = 1; // Line 5
} else { // Line 6
val = 2; // Line 7
} // Line 8
if (false) { // Line 9
val = 3; // Line 10
} else { // Line 11
val = 4; // Line 12
} // Line 13
return val; // Line 14
} // Line 15
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 3);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 4);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 0");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 5);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 0");
player.step();
// We skip over the false-branch.
REPORTER_ASSERT(r, player.getCurrentLine() == 9);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 1");
player.step();
// We skip over the true-branch.
REPORTER_ASSERT(r, player.getCurrentLine() == 12);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 1");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 14);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 4");
player.step();
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 4");
}
DEF_TEST(SkSLTracePlayerForLoop, r) {
sk_sp<SkSL::SkVMDebugTrace> trace = make_trace(r,
R"( // Line 1
int main() { // Line 2
int val = 0; // Line 3
for (int x = 1; x < 3; ++x) { // Line 4
val = x; // Line 5
} // Line 6
return val; // Line 7
} // Line 8
)");
SkSL::SkVMDebugTracePlayer player;
player.reset(trace);
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 3);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 4);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 0");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 5);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 0, x:0 = 1");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 4);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 1, x:0 = 1");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 5);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 1, x:0 = 2");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 4);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 2, x:0 = 2");
player.step();
REPORTER_ASSERT(r, player.getCurrentLine() == 7);
REPORTER_ASSERT(r, make_local_vars_string(*trace, player) == "val:0 = 2, x:0 = 2");
player.step();
REPORTER_ASSERT(r, player.traceHasCompleted());
REPORTER_ASSERT(r, make_global_vars_string(*trace, player) == "[main].result:0 = 2");
}