Flutter Engine
The Flutter Engine
SkSLDebugTracePlayer.cpp
Go to the documentation of this file.
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
9
11
12#include <limits.h>
13#include <algorithm>
14#include <utility>
15
16namespace SkSL {
17
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
50 this->tidyState();
51 while (!this->traceHasCompleted()) {
52 if (this->execute(fCursor++)) {
53 break;
54 }
55 }
56}
57
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
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
85 this->tidyState();
86 while (!this->traceHasCompleted()) {
87 if (this->execute(fCursor++)) {
88 if (this->atBreakpoint()) {
89 break;
90 }
91 }
92 }
93}
94
95void 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
106 return !fDebugTrace || fCursor >= fDebugTrace->fTraceInfo.size();
107}
108
110 SkASSERT(!fStack.empty());
111 return fStack.back().fLine;
112}
113
114int32_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
124 return fBreakpointLines.count(this->getCurrentLine());
125}
126
127void SkSLDebugTracePlayer::setBreakpoints(std::unordered_set<int> breakpointLines) {
128 fBreakpointLines = std::move(breakpointLines);
129}
130
132 fBreakpointLines.insert(line);
133}
134
136 fBreakpointLines.erase(line);
137}
138
139std::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
150 SkASSERT(!fStack.empty());
151 return fStack.size() - 1;
152}
153
154std::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
170std::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
182std::vector<SkSLDebugTracePlayer::VariableData> SkSLDebugTracePlayer::getGlobalVariables() const {
183 if (fStack.empty()) {
184 return {};
185 }
186 return this->getVariablesForDisplayMask(fStack.front().fDisplayMask);
187}
188
189void 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
212bool 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
#define SkDEBUGFAILF(fmt,...)
Definition: SkAssert.h:119
#define SkASSERT(cond)
Definition: SkAssert.h:116
void forEachSetIndex(FN f) const
Definition: SkBitSet.h:77
size_t size() const
Definition: SkBitSet.h:71
void reset(sk_sp< DebugTracePriv > trace)
std::vector< VariableData > getGlobalVariables() const
std::vector< VariableData > getLocalVariables(int stackFrameIndex) const
int32_t getCurrentLineInStackFrame(int stackFrameIndex) const
void setBreakpoints(std::unordered_set< int > breakpointLines)
std::vector< int > getCallStack() const
static bool b
struct MyStruct a[10]
uint8_t value
Definition: __init__.py:1