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