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