1 /*
2 * Copyright 2021 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "src/sksl/tracing/SkVMDebugTracePlayer.h"
9
10 namespace SkSL {
11
reset(sk_sp<SkVMDebugTrace> debugTrace)12 void SkVMDebugTracePlayer::reset(sk_sp<SkVMDebugTrace> debugTrace) {
13 size_t nslots = debugTrace ? debugTrace->fSlotInfo.size() : 0;
14 fDebugTrace = debugTrace;
15 fCursor = 0;
16 fScope = 0;
17 fSlots.clear();
18 fSlots.resize(nslots, {/*fValue=*/0,
19 /*fScope=*/INT_MAX,
20 /*fWriteTime=*/0});
21 fStack.clear();
22 fStack.push_back({/*fFunction=*/-1,
23 /*fLine=*/-1,
24 /*fDisplayMask=*/SkBitSet(nslots)});
25 fDirtyMask.emplace(nslots);
26 fReturnValues.emplace(nslots);
27
28 for (size_t slotIdx = 0; slotIdx < nslots; ++slotIdx) {
29 if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue >= 0) {
30 fReturnValues->set(slotIdx);
31 }
32 }
33
34 for (const SkVMTraceInfo& trace : fDebugTrace->fTraceInfo) {
35 if (trace.op == SkVMTraceInfo::Op::kLine) {
36 fLineNumbers[trace.data[0]] += 1;
37 }
38 }
39 }
40
step()41 void SkVMDebugTracePlayer::step() {
42 this->tidyState();
43 while (!this->traceHasCompleted()) {
44 if (this->execute(fCursor++)) {
45 break;
46 }
47 }
48 }
49
stepOver()50 void SkVMDebugTracePlayer::stepOver() {
51 this->tidyState();
52 size_t initialStackDepth = fStack.size();
53 while (!this->traceHasCompleted()) {
54 bool canEscapeFromThisStackDepth = (fStack.size() <= initialStackDepth);
55 if (this->execute(fCursor++)) {
56 if (canEscapeFromThisStackDepth || this->atBreakpoint()) {
57 break;
58 }
59 }
60 }
61 }
62
stepOut()63 void SkVMDebugTracePlayer::stepOut() {
64 this->tidyState();
65 size_t initialStackDepth = fStack.size();
66 while (!this->traceHasCompleted()) {
67 if (this->execute(fCursor++)) {
68 bool hasEscapedFromInitialStackDepth = (fStack.size() < initialStackDepth);
69 if (hasEscapedFromInitialStackDepth || this->atBreakpoint()) {
70 break;
71 }
72 }
73 }
74 }
75
run()76 void SkVMDebugTracePlayer::run() {
77 this->tidyState();
78 while (!this->traceHasCompleted()) {
79 if (this->execute(fCursor++)) {
80 if (this->atBreakpoint()) {
81 break;
82 }
83 }
84 }
85 }
86
tidyState()87 void SkVMDebugTracePlayer::tidyState() {
88 fDirtyMask->reset();
89
90 // Conceptually this is `fStack.back().fDisplayMask &= ~fReturnValues`, but SkBitSet doesn't
91 // support masking one set of bits against another.
92 fReturnValues->forEachSetIndex([&](int slot) {
93 fStack.back().fDisplayMask.reset(slot);
94 });
95 }
96
traceHasCompleted() const97 bool SkVMDebugTracePlayer::traceHasCompleted() const {
98 return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size();
99 }
100
getCurrentLine() const101 int32_t SkVMDebugTracePlayer::getCurrentLine() const {
102 SkASSERT(!fStack.empty());
103 return fStack.back().fLine;
104 }
105
getCurrentLineInStackFrame(int stackFrameIndex) const106 int32_t SkVMDebugTracePlayer::getCurrentLineInStackFrame(int stackFrameIndex) const {
107 // The first entry on the stack is the "global" frame before we enter main, so offset our index
108 // by one to account for it.
109 ++stackFrameIndex;
110 SkASSERT(stackFrameIndex > 0);
111 SkASSERT((size_t)stackFrameIndex < fStack.size());
112 return fStack[stackFrameIndex].fLine;
113 }
114
atBreakpoint() const115 bool SkVMDebugTracePlayer::atBreakpoint() const {
116 return fBreakpointLines.count(this->getCurrentLine());
117 }
118
setBreakpoints(std::unordered_set<int> breakpointLines)119 void SkVMDebugTracePlayer::setBreakpoints(std::unordered_set<int> breakpointLines) {
120 fBreakpointLines = std::move(breakpointLines);
121 }
122
addBreakpoint(int line)123 void SkVMDebugTracePlayer::addBreakpoint(int line) {
124 fBreakpointLines.insert(line);
125 }
126
removeBreakpoint(int line)127 void SkVMDebugTracePlayer::removeBreakpoint(int line) {
128 fBreakpointLines.erase(line);
129 }
130
getCallStack() const131 std::vector<int> SkVMDebugTracePlayer::getCallStack() const {
132 SkASSERT(!fStack.empty());
133 std::vector<int> funcs;
134 funcs.reserve(fStack.size() - 1);
135 for (size_t index = 1; index < fStack.size(); ++index) {
136 funcs.push_back(fStack[index].fFunction);
137 }
138 return funcs;
139 }
140
getStackDepth() const141 int SkVMDebugTracePlayer::getStackDepth() const {
142 SkASSERT(!fStack.empty());
143 return fStack.size() - 1;
144 }
145
getVariablesForDisplayMask(const SkBitSet & displayMask) const146 std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getVariablesForDisplayMask(
147 const SkBitSet& displayMask) const {
148 SkASSERT(displayMask.size() == fSlots.size());
149
150 std::vector<VariableData> vars;
151 displayMask.forEachSetIndex([&](int slot) {
152 double typedValue = fDebugTrace->interpretValueBits(slot, fSlots[slot].fValue);
153 vars.push_back({slot, fDirtyMask->test(slot), typedValue});
154 });
155 // Order the variable list so that the most recently-written variables are shown at the top.
156 std::stable_sort(vars.begin(), vars.end(), [&](const VariableData& a, const VariableData& b) {
157 return fSlots[a.fSlotIndex].fWriteTime > fSlots[b.fSlotIndex].fWriteTime;
158 });
159 return vars;
160 }
161
getLocalVariables(int stackFrameIndex) const162 std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getLocalVariables(
163 int stackFrameIndex) const {
164 // The first entry on the stack is the "global" frame before we enter main, so offset our index
165 // by one to account for it.
166 ++stackFrameIndex;
167 if (stackFrameIndex <= 0 || (size_t)stackFrameIndex >= fStack.size()) {
168 SkDEBUGFAILF("stack frame %d doesn't exist", stackFrameIndex - 1);
169 return {};
170 }
171 return this->getVariablesForDisplayMask(fStack[stackFrameIndex].fDisplayMask);
172 }
173
getGlobalVariables() const174 std::vector<SkVMDebugTracePlayer::VariableData> SkVMDebugTracePlayer::getGlobalVariables() const {
175 if (fStack.empty()) {
176 return {};
177 }
178 return this->getVariablesForDisplayMask(fStack.front().fDisplayMask);
179 }
180
updateVariableWriteTime(int slotIdx,size_t cursor)181 void SkVMDebugTracePlayer::updateVariableWriteTime(int slotIdx, size_t cursor) {
182 // The slotIdx could point to any slot within a variable.
183 // We want to update the write time on EVERY slot associated with this variable.
184 // The SlotInfo's groupIndex gives us enough information to find the affected range.
185 const SkSL::SkVMSlotInfo& changedSlot = fDebugTrace->fSlotInfo[slotIdx];
186 slotIdx -= changedSlot.groupIndex;
187 SkASSERT(slotIdx >= 0);
188 SkASSERT(slotIdx < (int)fDebugTrace->fSlotInfo.size());
189
190 for (;;) {
191 fSlots[slotIdx++].fWriteTime = cursor;
192
193 // Stop if we've reached the final slot.
194 if (slotIdx >= (int)fDebugTrace->fSlotInfo.size()) {
195 break;
196 }
197 // Each separate variable-group starts with a groupIndex of 0; stop when we detect this.
198 if (fDebugTrace->fSlotInfo[slotIdx].groupIndex == 0) {
199 break;
200 }
201 }
202 }
203
execute(size_t position)204 bool SkVMDebugTracePlayer::execute(size_t position) {
205 if (position >= fDebugTrace->fTraceInfo.size()) {
206 SkDEBUGFAILF("position %zu out of range", position);
207 return true;
208 }
209
210 const SkVMTraceInfo& trace = fDebugTrace->fTraceInfo[position];
211 switch (trace.op) {
212 case SkVMTraceInfo::Op::kLine: { // data: line number, (unused)
213 SkASSERT(!fStack.empty());
214 int lineNumber = trace.data[0];
215 SkASSERT(lineNumber >= 0);
216 SkASSERT((size_t)lineNumber < fDebugTrace->fSource.size());
217 SkASSERT(fLineNumbers[lineNumber] > 0);
218 fStack.back().fLine = lineNumber;
219 fLineNumbers[lineNumber] -= 1;
220 return true;
221 }
222 case SkVMTraceInfo::Op::kVar: { // data: slot, value
223 int slotIdx = trace.data[0];
224 int value = trace.data[1];
225 SkASSERT(slotIdx >= 0);
226 SkASSERT((size_t)slotIdx < fDebugTrace->fSlotInfo.size());
227 fSlots[slotIdx].fValue = value;
228 fSlots[slotIdx].fScope = std::min<>(fSlots[slotIdx].fScope, fScope);
229 this->updateVariableWriteTime(slotIdx, position);
230 if (fDebugTrace->fSlotInfo[slotIdx].fnReturnValue < 0) {
231 // Normal variables are associated with the current function.
232 SkASSERT(fStack.size() > 0);
233 fStack.rbegin()[0].fDisplayMask.set(slotIdx);
234 } else {
235 // Return values are associated with the parent function (since the current function
236 // is exiting and we won't see them there).
237 SkASSERT(fStack.size() > 1);
238 fStack.rbegin()[1].fDisplayMask.set(slotIdx);
239 }
240 fDirtyMask->set(slotIdx);
241 break;
242 }
243 case SkVMTraceInfo::Op::kEnter: { // data: function index, (unused)
244 int fnIdx = trace.data[0];
245 SkASSERT(fnIdx >= 0);
246 SkASSERT((size_t)fnIdx < fDebugTrace->fFuncInfo.size());
247 fStack.push_back({/*fFunction=*/fnIdx,
248 /*fLine=*/-1,
249 /*fDisplayMask=*/SkBitSet(fDebugTrace->fSlotInfo.size())});
250 break;
251 }
252 case SkVMTraceInfo::Op::kExit: { // data: function index, (unused)
253 SkASSERT(!fStack.empty());
254 SkASSERT(fStack.back().fFunction == trace.data[0]);
255 fStack.pop_back();
256 return true;
257 }
258 case SkVMTraceInfo::Op::kScope: { // data: scope delta, (unused)
259 SkASSERT(!fStack.empty());
260 fScope += trace.data[0];
261 if (trace.data[0] < 0) {
262 // If the scope is being reduced, discard variables that are now out of scope.
263 for (size_t slotIdx = 0; slotIdx < fSlots.size(); ++slotIdx) {
264 if (fScope < fSlots[slotIdx].fScope) {
265 fSlots[slotIdx].fScope = INT_MAX;
266 fStack.back().fDisplayMask.reset(slotIdx);
267 }
268 }
269 }
270 return false;
271 }
272 }
273
274 return false;
275 }
276
277 } // namespace SkSL
278