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