/src/skia/src/sksl/analysis/SkSLProgramUsage.cpp
Line | Count | Source (jump to first uncovered line) |
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 "include/core/SkSpan.h" |
9 | | #include "include/core/SkTypes.h" |
10 | | #include "include/private/base/SkDebug.h" |
11 | | #include "src/base/SkEnumBitMask.h" |
12 | | #include "src/core/SkTHash.h" |
13 | | #include "src/sksl/SkSLAnalysis.h" |
14 | | #include "src/sksl/SkSLCompiler.h" |
15 | | #include "src/sksl/analysis/SkSLProgramUsage.h" |
16 | | #include "src/sksl/analysis/SkSLProgramVisitor.h" |
17 | | #include "src/sksl/ir/SkSLExpression.h" |
18 | | #include "src/sksl/ir/SkSLFunctionCall.h" |
19 | | #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
20 | | #include "src/sksl/ir/SkSLFunctionDefinition.h" |
21 | | #include "src/sksl/ir/SkSLInterfaceBlock.h" |
22 | | #include "src/sksl/ir/SkSLModifierFlags.h" |
23 | | #include "src/sksl/ir/SkSLProgramElement.h" |
24 | | #include "src/sksl/ir/SkSLStatement.h" |
25 | | #include "src/sksl/ir/SkSLStructDefinition.h" |
26 | | #include "src/sksl/ir/SkSLSymbol.h" |
27 | | #include "src/sksl/ir/SkSLType.h" |
28 | | #include "src/sksl/ir/SkSLVarDeclarations.h" |
29 | | #include "src/sksl/ir/SkSLVariable.h" |
30 | | #include "src/sksl/ir/SkSLVariableReference.h" |
31 | | |
32 | | #include <cstring> |
33 | | #include <memory> |
34 | | #include <string_view> |
35 | | #include <vector> |
36 | | |
37 | | namespace SkSL { |
38 | | |
39 | | struct Program; |
40 | | |
41 | | namespace { |
42 | | |
43 | | class ProgramUsageVisitor : public ProgramVisitor { |
44 | | public: |
45 | 1.34k | ProgramUsageVisitor(ProgramUsage* usage, int delta) : fUsage(usage), fDelta(delta) {} |
46 | | |
47 | 3.77k | bool visitProgramElement(const ProgramElement& pe) override { |
48 | 3.77k | if (pe.is<FunctionDefinition>()) { |
49 | 983 | for (const Variable* param : pe.as<FunctionDefinition>().declaration().parameters()) { |
50 | | // Ensure function-parameter variables exist in the variable usage map. They aren't |
51 | | // otherwise declared, but ProgramUsage::get() should be able to find them, even if |
52 | | // they are unread and unwritten. |
53 | 637 | ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[param]; |
54 | 637 | counts.fVarExists += fDelta; |
55 | | |
56 | 637 | this->visitType(param->type()); |
57 | 637 | } |
58 | 2.79k | } else if (pe.is<InterfaceBlock>()) { |
59 | | // Ensure interface-block variables exist in the variable usage map. |
60 | 0 | const Variable* var = pe.as<InterfaceBlock>().var(); |
61 | 0 | fUsage->fVariableCounts[var]; |
62 | |
|
63 | 0 | this->visitType(var->type()); |
64 | 2.79k | } else if (pe.is<StructDefinition>()) { |
65 | | // Ensure that structs referenced as nested types in other structs are counted as used. |
66 | 9 | this->visitStructFields(pe.as<StructDefinition>().type()); |
67 | 9 | } |
68 | 3.77k | return INHERITED::visitProgramElement(pe); |
69 | 3.77k | } |
70 | | |
71 | 8.93k | bool visitStatement(const Statement& s) override { |
72 | 8.93k | if (s.is<VarDeclaration>()) { |
73 | | // Add all declared variables to the usage map (even if never otherwise accessed). |
74 | 2.80k | const VarDeclaration& vd = s.as<VarDeclaration>(); |
75 | 2.80k | const Variable* var = vd.var(); |
76 | 2.80k | ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[var]; |
77 | 2.80k | counts.fVarExists += fDelta; |
78 | 2.80k | SkASSERT(counts.fVarExists >= 0 && counts.fVarExists <= 1); |
79 | 2.80k | if (vd.value()) { |
80 | | // The initial-value expression, when present, counts as a write. |
81 | 1.33k | counts.fWrite += fDelta; |
82 | 1.33k | } |
83 | 2.80k | this->visitType(var->type()); |
84 | 2.80k | } |
85 | 8.93k | return INHERITED::visitStatement(s); |
86 | 8.93k | } SkSLProgramUsage.cpp:SkSL::(anonymous namespace)::ProgramUsageVisitor::visitStatement(SkSL::Statement const&) Line | Count | Source | 71 | 8.93k | bool visitStatement(const Statement& s) override { | 72 | 8.93k | if (s.is<VarDeclaration>()) { | 73 | | // Add all declared variables to the usage map (even if never otherwise accessed). | 74 | 2.80k | const VarDeclaration& vd = s.as<VarDeclaration>(); | 75 | 2.80k | const Variable* var = vd.var(); | 76 | 2.80k | ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[var]; | 77 | 2.80k | counts.fVarExists += fDelta; | 78 | 2.80k | SkASSERT(counts.fVarExists >= 0 && counts.fVarExists <= 1); | 79 | 2.80k | if (vd.value()) { | 80 | | // The initial-value expression, when present, counts as a write. | 81 | 1.33k | counts.fWrite += fDelta; | 82 | 1.33k | } | 83 | 2.80k | this->visitType(var->type()); | 84 | 2.80k | } | 85 | 8.93k | return INHERITED::visitStatement(s); | 86 | 8.93k | } |
Unexecuted instantiation: SkSLProgramUsage.cpp:SkSL::(anonymous namespace)::ProgramUsageVisitor::visitStatement(SkSL::Statement const&) |
87 | | |
88 | 24.2k | bool visitExpression(const Expression& e) override { |
89 | 24.2k | this->visitType(e.type()); |
90 | 24.2k | if (e.is<FunctionCall>()) { |
91 | 1.95k | const FunctionDeclaration* f = &e.as<FunctionCall>().function(); |
92 | 1.95k | fUsage->fCallCounts[f] += fDelta; |
93 | 1.95k | SkASSERT(fUsage->fCallCounts[f] >= 0); |
94 | 22.2k | } else if (e.is<VariableReference>()) { |
95 | 7.74k | const VariableReference& ref = e.as<VariableReference>(); |
96 | 7.74k | ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()]; |
97 | 7.74k | switch (ref.refKind()) { |
98 | 6.49k | case VariableRefKind::kRead: |
99 | 6.49k | counts.fRead += fDelta; |
100 | 6.49k | break; |
101 | 1.22k | case VariableRefKind::kWrite: |
102 | 1.22k | counts.fWrite += fDelta; |
103 | 1.22k | break; |
104 | 22 | case VariableRefKind::kReadWrite: |
105 | 22 | case VariableRefKind::kPointer: |
106 | 22 | counts.fRead += fDelta; |
107 | 22 | counts.fWrite += fDelta; |
108 | 22 | break; |
109 | 7.74k | } |
110 | 7.74k | SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0); |
111 | 7.74k | } |
112 | 24.2k | return INHERITED::visitExpression(e); |
113 | 24.2k | } SkSLProgramUsage.cpp:SkSL::(anonymous namespace)::ProgramUsageVisitor::visitExpression(SkSL::Expression const&) Line | Count | Source | 88 | 24.2k | bool visitExpression(const Expression& e) override { | 89 | 24.2k | this->visitType(e.type()); | 90 | 24.2k | if (e.is<FunctionCall>()) { | 91 | 1.95k | const FunctionDeclaration* f = &e.as<FunctionCall>().function(); | 92 | 1.95k | fUsage->fCallCounts[f] += fDelta; | 93 | 1.95k | SkASSERT(fUsage->fCallCounts[f] >= 0); | 94 | 22.2k | } else if (e.is<VariableReference>()) { | 95 | 7.74k | const VariableReference& ref = e.as<VariableReference>(); | 96 | 7.74k | ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()]; | 97 | 7.74k | switch (ref.refKind()) { | 98 | 6.49k | case VariableRefKind::kRead: | 99 | 6.49k | counts.fRead += fDelta; | 100 | 6.49k | break; | 101 | 1.22k | case VariableRefKind::kWrite: | 102 | 1.22k | counts.fWrite += fDelta; | 103 | 1.22k | break; | 104 | 22 | case VariableRefKind::kReadWrite: | 105 | 22 | case VariableRefKind::kPointer: | 106 | 22 | counts.fRead += fDelta; | 107 | 22 | counts.fWrite += fDelta; | 108 | 22 | break; | 109 | 7.74k | } | 110 | 7.74k | SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0); | 111 | 7.74k | } | 112 | 24.2k | return INHERITED::visitExpression(e); | 113 | 24.2k | } |
Unexecuted instantiation: SkSLProgramUsage.cpp:SkSL::(anonymous namespace)::ProgramUsageVisitor::visitExpression(SkSL::Expression const&) |
114 | | |
115 | 28.1k | void visitType(const Type& t) { |
116 | 28.1k | if (t.isArray()) { |
117 | 387 | this->visitType(t.componentType()); |
118 | 387 | return; |
119 | 387 | } |
120 | 27.7k | if (t.isStruct()) { |
121 | 33 | int& structCount = fUsage->fStructCounts[&t]; |
122 | 33 | structCount += fDelta; |
123 | 33 | SkASSERT(structCount >= 0); |
124 | | |
125 | 33 | this->visitStructFields(t); |
126 | 33 | } |
127 | 27.7k | } |
128 | | |
129 | 42 | void visitStructFields(const Type& t) { |
130 | 91 | for (const Field& f : t.fields()) { |
131 | 91 | this->visitType(*f.fType); |
132 | 91 | } |
133 | 42 | } |
134 | | |
135 | | using ProgramVisitor::visitProgramElement; |
136 | | using ProgramVisitor::visitStatement; |
137 | | |
138 | | ProgramUsage* fUsage; |
139 | | int fDelta; |
140 | | using INHERITED = ProgramVisitor; |
141 | | }; |
142 | | |
143 | | } // namespace |
144 | | |
145 | 340 | std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Program& program) { |
146 | 340 | auto usage = std::make_unique<ProgramUsage>(); |
147 | 340 | ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1); |
148 | 340 | addRefs.visit(program); |
149 | 340 | return usage; |
150 | 340 | } |
151 | | |
152 | 22 | std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Module& module) { |
153 | 22 | auto usage = std::make_unique<ProgramUsage>(); |
154 | 22 | ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1); |
155 | | |
156 | 86 | for (const Module* m = &module; m != nullptr; m = m->fParent) { |
157 | 2.13k | for (const std::unique_ptr<ProgramElement>& element : m->fElements) { |
158 | 2.13k | addRefs.visitProgramElement(*element); |
159 | 2.13k | } |
160 | 64 | } |
161 | 22 | return usage; |
162 | 22 | } |
163 | | |
164 | 1.29k | ProgramUsage::VariableCounts ProgramUsage::get(const Variable& v) const { |
165 | 1.29k | const VariableCounts* counts = fVariableCounts.find(&v); |
166 | 1.29k | SkASSERT(counts); |
167 | 1.29k | return *counts; |
168 | 1.29k | } |
169 | | |
170 | 874 | bool ProgramUsage::isDead(const Variable& v) const { |
171 | 874 | ModifierFlags flags = v.modifierFlags(); |
172 | 874 | VariableCounts counts = this->get(v); |
173 | 874 | if (flags & (ModifierFlag::kIn | ModifierFlag::kOut | ModifierFlag::kUniform)) { |
174 | | // Never eliminate ins, outs, or uniforms. |
175 | 323 | return false; |
176 | 323 | } |
177 | 551 | if (v.type().componentType().isOpaque()) { |
178 | | // Never eliminate samplers, runtime-effect children, or atomics. |
179 | 0 | return false; |
180 | 0 | } |
181 | | // Consider the variable dead if it's never read and never written (besides the initial-value). |
182 | 551 | return !counts.fRead && (counts.fWrite <= (v.initialValue() ? 1 : 0)); |
183 | 551 | } |
184 | | |
185 | 628 | int ProgramUsage::get(const FunctionDeclaration& f) const { |
186 | 628 | const int* count = fCallCounts.find(&f); |
187 | 628 | return count ? *count : 0; |
188 | 628 | } |
189 | | |
190 | 76 | void ProgramUsage::add(const Expression* expr) { |
191 | 76 | ProgramUsageVisitor addRefs(this, /*delta=*/+1); |
192 | 76 | addRefs.visitExpression(*expr); |
193 | 76 | } |
194 | | |
195 | 76 | void ProgramUsage::add(const Statement* stmt) { |
196 | 76 | ProgramUsageVisitor addRefs(this, /*delta=*/+1); |
197 | 76 | addRefs.visitStatement(*stmt); |
198 | 76 | } |
199 | | |
200 | 27 | void ProgramUsage::add(const ProgramElement& element) { |
201 | 27 | ProgramUsageVisitor addRefs(this, /*delta=*/+1); |
202 | 27 | addRefs.visitProgramElement(element); |
203 | 27 | } |
204 | | |
205 | 76 | void ProgramUsage::remove(const Expression* expr) { |
206 | 76 | ProgramUsageVisitor subRefs(this, /*delta=*/-1); |
207 | 76 | subRefs.visitExpression(*expr); |
208 | 76 | } |
209 | | |
210 | 493 | void ProgramUsage::remove(const Statement* stmt) { |
211 | 493 | ProgramUsageVisitor subRefs(this, /*delta=*/-1); |
212 | 493 | subRefs.visitStatement(*stmt); |
213 | 493 | } |
214 | | |
215 | 230 | void ProgramUsage::remove(const ProgramElement& element) { |
216 | 230 | ProgramUsageVisitor subRefs(this, /*delta=*/-1); |
217 | 230 | subRefs.visitProgramElement(element); |
218 | 230 | } |
219 | | |
220 | 0 | static bool contains_matching_data(const ProgramUsage& a, const ProgramUsage& b) { |
221 | 0 | constexpr bool kReportMismatch = false; |
222 | |
|
223 | 0 | for (const auto& [varA, varCountA] : a.fVariableCounts) { |
224 | | // Skip variable entries with zero reported usage. |
225 | 0 | if (!varCountA.fVarExists && !varCountA.fRead && !varCountA.fWrite) { |
226 | 0 | continue; |
227 | 0 | } |
228 | | // Find the matching variable in the other map and ensure that its counts match. |
229 | 0 | const ProgramUsage::VariableCounts* varCountB = b.fVariableCounts.find(varA); |
230 | 0 | if (!varCountB || 0 != memcmp(&varCountA, varCountB, sizeof(varCountA))) { |
231 | 0 | if constexpr (kReportMismatch) { |
232 | 0 | SkDebugf("VariableCounts mismatch: '%.*s' (E%d R%d W%d != E%d R%d W%d)\n", |
233 | 0 | (int)varA->name().size(), varA->name().data(), |
234 | 0 | varCountA.fVarExists, |
235 | 0 | varCountA.fRead, |
236 | 0 | varCountA.fWrite, |
237 | 0 | varCountB ? varCountB->fVarExists : 0, |
238 | 0 | varCountB ? varCountB->fRead : 0, |
239 | 0 | varCountB ? varCountB->fWrite : 0); |
240 | 0 | } |
241 | 0 | return false; |
242 | 0 | } |
243 | 0 | } |
244 | | |
245 | 0 | for (const auto& [callA, callCountA] : a.fCallCounts) { |
246 | | // Skip function-call entries with zero reported usage. |
247 | 0 | if (!callCountA) { |
248 | 0 | continue; |
249 | 0 | } |
250 | | // Find the matching function in the other map and ensure that its call-count matches. |
251 | 0 | const int* callCountB = b.fCallCounts.find(callA); |
252 | 0 | if (!callCountB || callCountA != *callCountB) { |
253 | 0 | if constexpr (kReportMismatch) { |
254 | 0 | SkDebugf("CallCounts mismatch: '%.*s' (%d != %d)\n", |
255 | 0 | (int)callA->name().size(), callA->name().data(), |
256 | 0 | callCountA, |
257 | 0 | callCountB ? *callCountB : 0); |
258 | 0 | } |
259 | 0 | return false; |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | 0 | for (const auto& [structA, structCountA] : a.fStructCounts) { |
264 | | // Skip struct entries with zero reported usage. |
265 | 0 | if (!structCountA) { |
266 | 0 | continue; |
267 | 0 | } |
268 | | // Find the matching struct in the other map and ensure that its usage-count matches. |
269 | 0 | const int* structCountB = b.fStructCounts.find(structA); |
270 | 0 | if (!structCountB || structCountA != *structCountB) { |
271 | 0 | if constexpr (kReportMismatch) { |
272 | 0 | SkDebugf("StructCounts mismatch: '%.*s' (%d != %d)\n", |
273 | 0 | (int)structA->name().size(), structA->name().data(), |
274 | 0 | structCountA, |
275 | 0 | structCountB ? *structCountB : 0); |
276 | 0 | } |
277 | 0 | return false; |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | // Every non-zero entry in A has a matching non-zero entry in B. |
282 | 0 | return true; |
283 | 0 | } |
284 | | |
285 | 0 | bool ProgramUsage::operator==(const ProgramUsage& that) const { |
286 | | // ProgramUsage can be "equal" while the underlying hash maps look slightly different, because a |
287 | | // dead-stripped variable or function will have a usage count of zero, but will still exist in |
288 | | // the maps. If the program usage is re-analyzed from scratch, the maps will not contain an |
289 | | // entry for these variables or functions at all. This means our maps can be "equal" while |
290 | | // having different element counts. |
291 | | // |
292 | | // In order to check these maps, we compare map entries bi-directionally, skipping zero-usage |
293 | | // entries. If all the non-zero elements in `this` match the elements in `that`, and all the |
294 | | // non-zero elements in `that` match the elements in `this`, all the non-zero elements must be |
295 | | // identical, and all the zero elements must be either zero or non-existent on both sides. |
296 | 0 | return contains_matching_data(*this, that) && |
297 | 0 | contains_matching_data(that, *this); |
298 | 0 | } |
299 | | |
300 | | } // namespace SkSL |