• 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/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