Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkSLProgramUsage.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
12#include "src/core/SkTHash.h"
31
32#include <cstring>
33#include <memory>
34#include <string_view>
35#include <vector>
36
37namespace SkSL {
38
39struct Program;
40
41namespace {
42
43class ProgramUsageVisitor : public ProgramVisitor {
44public:
45 ProgramUsageVisitor(ProgramUsage* usage, int delta) : fUsage(usage), fDelta(delta) {}
46
47 bool visitProgramElement(const ProgramElement& pe) override {
48 if (pe.is<FunctionDefinition>()) {
49 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 ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[param];
54 counts.fVarExists += fDelta;
55
56 this->visitType(param->type());
57 }
58 } else if (pe.is<InterfaceBlock>()) {
59 // Ensure interface-block variables exist in the variable usage map.
60 const Variable* var = pe.as<InterfaceBlock>().var();
61 fUsage->fVariableCounts[var];
62
63 this->visitType(var->type());
64 } else if (pe.is<StructDefinition>()) {
65 // Ensure that structs referenced as nested types in other structs are counted as used.
66 this->visitStructFields(pe.as<StructDefinition>().type());
67 }
68 return INHERITED::visitProgramElement(pe);
69 }
70
71 bool visitStatement(const Statement& s) override {
72 if (s.is<VarDeclaration>()) {
73 // Add all declared variables to the usage map (even if never otherwise accessed).
74 const VarDeclaration& vd = s.as<VarDeclaration>();
75 const Variable* var = vd.var();
76 ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[var];
77 counts.fVarExists += fDelta;
78 SkASSERT(counts.fVarExists >= 0 && counts.fVarExists <= 1);
79 if (vd.value()) {
80 // The initial-value expression, when present, counts as a write.
81 counts.fWrite += fDelta;
82 }
83 this->visitType(var->type());
84 }
85 return INHERITED::visitStatement(s);
86 }
87
88 bool visitExpression(const Expression& e) override {
89 this->visitType(e.type());
90 if (e.is<FunctionCall>()) {
91 const FunctionDeclaration* f = &e.as<FunctionCall>().function();
92 fUsage->fCallCounts[f] += fDelta;
93 SkASSERT(fUsage->fCallCounts[f] >= 0);
94 } else if (e.is<VariableReference>()) {
95 const VariableReference& ref = e.as<VariableReference>();
96 ProgramUsage::VariableCounts& counts = fUsage->fVariableCounts[ref.variable()];
97 switch (ref.refKind()) {
98 case VariableRefKind::kRead:
99 counts.fRead += fDelta;
100 break;
101 case VariableRefKind::kWrite:
102 counts.fWrite += fDelta;
103 break;
104 case VariableRefKind::kReadWrite:
105 case VariableRefKind::kPointer:
106 counts.fRead += fDelta;
107 counts.fWrite += fDelta;
108 break;
109 }
110 SkASSERT(counts.fRead >= 0 && counts.fWrite >= 0);
111 }
112 return INHERITED::visitExpression(e);
113 }
114
115 void visitType(const Type& t) {
116 if (t.isArray()) {
117 this->visitType(t.componentType());
118 return;
119 }
120 if (t.isStruct()) {
121 int& structCount = fUsage->fStructCounts[&t];
122 structCount += fDelta;
123 SkASSERT(structCount >= 0);
124
125 this->visitStructFields(t);
126 }
127 }
128
129 void visitStructFields(const Type& t) {
130 for (const Field& f : t.fields()) {
131 this->visitType(*f.fType);
132 }
133 }
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
145std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Program& program) {
146 auto usage = std::make_unique<ProgramUsage>();
147 ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1);
148 addRefs.visit(program);
149 return usage;
150}
151
152std::unique_ptr<ProgramUsage> Analysis::GetUsage(const Module& module) {
153 auto usage = std::make_unique<ProgramUsage>();
154 ProgramUsageVisitor addRefs(usage.get(), /*delta=*/+1);
155
156 for (const Module* m = &module; m != nullptr; m = m->fParent) {
157 for (const std::unique_ptr<ProgramElement>& element : m->fElements) {
158 addRefs.visitProgramElement(*element);
159 }
160 }
161 return usage;
162}
163
165 const VariableCounts* counts = fVariableCounts.find(&v);
166 SkASSERT(counts);
167 return *counts;
168}
169
170bool ProgramUsage::isDead(const Variable& v) const {
172 VariableCounts counts = this->get(v);
174 // Never eliminate ins, outs, or uniforms.
175 return false;
176 }
177 if (v.type().componentType().isOpaque()) {
178 // Never eliminate samplers, runtime-effect children, or atomics.
179 return false;
180 }
181 // Consider the variable dead if it's never read and never written (besides the initial-value).
182 return !counts.fRead && (counts.fWrite <= (v.initialValue() ? 1 : 0));
183}
184
186 const int* count = fCallCounts.find(&f);
187 return count ? *count : 0;
188}
189
190void ProgramUsage::add(const Expression* expr) {
191 ProgramUsageVisitor addRefs(this, /*delta=*/+1);
192 addRefs.visitExpression(*expr);
193}
194
195void ProgramUsage::add(const Statement* stmt) {
196 ProgramUsageVisitor addRefs(this, /*delta=*/+1);
197 addRefs.visitStatement(*stmt);
198}
199
200void ProgramUsage::add(const ProgramElement& element) {
201 ProgramUsageVisitor addRefs(this, /*delta=*/+1);
202 addRefs.visitProgramElement(element);
203}
204
206 ProgramUsageVisitor subRefs(this, /*delta=*/-1);
207 subRefs.visitExpression(*expr);
208}
209
211 ProgramUsageVisitor subRefs(this, /*delta=*/-1);
212 subRefs.visitStatement(*stmt);
213}
214
216 ProgramUsageVisitor subRefs(this, /*delta=*/-1);
217 subRefs.visitProgramElement(element);
218}
219
221 constexpr bool kReportMismatch = false;
222
223 for (const auto& [varA, varCountA] : a.fVariableCounts) {
224 // Skip variable entries with zero reported usage.
225 if (!varCountA.fVarExists && !varCountA.fRead && !varCountA.fWrite) {
226 continue;
227 }
228 // Find the matching variable in the other map and ensure that its counts match.
229 const ProgramUsage::VariableCounts* varCountB = b.fVariableCounts.find(varA);
230 if (!varCountB || 0 != memcmp(&varCountA, varCountB, sizeof(varCountA))) {
231 if constexpr (kReportMismatch) {
232 SkDebugf("VariableCounts mismatch: '%.*s' (E%d R%d W%d != E%d R%d W%d)\n",
233 (int)varA->name().size(), varA->name().data(),
234 varCountA.fVarExists,
235 varCountA.fRead,
236 varCountA.fWrite,
237 varCountB ? varCountB->fVarExists : 0,
238 varCountB ? varCountB->fRead : 0,
239 varCountB ? varCountB->fWrite : 0);
240 }
241 return false;
242 }
243 }
244
245 for (const auto& [callA, callCountA] : a.fCallCounts) {
246 // Skip function-call entries with zero reported usage.
247 if (!callCountA) {
248 continue;
249 }
250 // Find the matching function in the other map and ensure that its call-count matches.
251 const int* callCountB = b.fCallCounts.find(callA);
252 if (!callCountB || callCountA != *callCountB) {
253 if constexpr (kReportMismatch) {
254 SkDebugf("CallCounts mismatch: '%.*s' (%d != %d)\n",
255 (int)callA->name().size(), callA->name().data(),
256 callCountA,
257 callCountB ? *callCountB : 0);
258 }
259 return false;
260 }
261 }
262
263 for (const auto& [structA, structCountA] : a.fStructCounts) {
264 // Skip struct entries with zero reported usage.
265 if (!structCountA) {
266 continue;
267 }
268 // Find the matching struct in the other map and ensure that its usage-count matches.
269 const int* structCountB = b.fStructCounts.find(structA);
270 if (!structCountB || structCountA != *structCountB) {
271 if constexpr (kReportMismatch) {
272 SkDebugf("StructCounts mismatch: '%.*s' (%d != %d)\n",
273 (int)structA->name().size(), structA->name().data(),
274 structCountA,
275 structCountB ? *structCountB : 0);
276 }
277 return false;
278 }
279 }
280
281 // Every non-zero entry in A has a matching non-zero entry in B.
282 return true;
283}
284
285bool 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 return contains_matching_data(*this, that) &&
297 contains_matching_data(that, *this);
298}
299
300} // namespace SkSL
int count
#define SkASSERT(cond)
Definition SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
#define INHERITED(method,...)
void add(const Expression *expr)
VariableCounts get(const Variable &) const
bool operator==(const ProgramUsage &that) const
void remove(const Expression *expr)
bool isDead(const Variable &) const
skia_private::THashMap< const Variable *, VariableCounts > fVariableCounts
skia_private::THashMap< const Symbol *, int > fCallCounts
const Type & type() const
Definition SkSLSymbol.h:42
virtual const Type & componentType() const
Definition SkSLType.h:404
bool isOpaque() const
Definition SkSLType.h:353
const Expression * initialValue() const
ModifierFlags modifierFlags() const
static bool b
struct MyStruct s
struct MyStruct a[10]
FlutterSemanticsFlag flags
Dart_NativeFunction function
Definition fuchsia.cc:51
std::unique_ptr< ProgramUsage > GetUsage(const Program &program)
static bool contains_matching_data(const ProgramUsage &a, const ProgramUsage &b)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core counts
Definition switches.h:239
static void usage(char *argv0)