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/SkSLProgramElement.h"
11 #include "include/private/SkSLStatement.h"
12 #include "src/core/SkTHash.h"
13 #include "src/sksl/SkSLAnalysis.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLProgramSettings.h"
16 #include "src/sksl/analysis/SkSLProgramUsage.h"
17 #include "src/sksl/ir/SkSLBinaryExpression.h"
18 #include "src/sksl/ir/SkSLExpression.h"
19 #include "src/sksl/ir/SkSLExpressionStatement.h"
20 #include "src/sksl/ir/SkSLFunctionDefinition.h"
21 #include "src/sksl/ir/SkSLNop.h"
22 #include "src/sksl/ir/SkSLProgram.h"
23 #include "src/sksl/ir/SkSLVarDeclarations.h"
24 #include "src/sksl/ir/SkSLVariable.h"
25 #include "src/sksl/ir/SkSLVariableReference.h"
26 #include "src/sksl/transform/SkSLProgramWriter.h"
27 #include "src/sksl/transform/SkSLTransform.h"
28
29 #include <memory>
30 #include <utility>
31 #include <vector>
32
33 namespace SkSL {
34
35 class Context;
36
eliminate_dead_local_variables(const Context & context,SkSpan<std::unique_ptr<ProgramElement>> elements,ProgramUsage * usage)37 static bool eliminate_dead_local_variables(const Context& context,
38 SkSpan<std::unique_ptr<ProgramElement>> elements,
39 ProgramUsage* usage) {
40 class DeadLocalVariableEliminator : public ProgramWriter {
41 public:
42 DeadLocalVariableEliminator(const Context& context, ProgramUsage* usage)
43 : fContext(context)
44 , fUsage(usage) {}
45
46 using ProgramWriter::visitProgramElement;
47
48 bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
49 if (expr->is<BinaryExpression>()) {
50 // Search for expressions of the form `deadVar = anyExpression`.
51 BinaryExpression& binary = expr->as<BinaryExpression>();
52 if (VariableReference* assignedVar = binary.isAssignmentIntoVariable()) {
53 if (fDeadVariables.contains(assignedVar->variable())) {
54 // Replace `deadVar = anyExpression` with `anyExpression`.
55 fUsage->remove(binary.left().get());
56 expr = std::move(binary.right());
57
58 // If `anyExpression` is now a lone ExpressionStatement, it's highly likely
59 // that we can eliminate it entirely. This flag will let us know to check.
60 fAssignmentWasEliminated = true;
61
62 // Re-process the newly cleaned-up expression. This lets us fully clean up
63 // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
64 // or silly double-assignments like `a = a = 123;`.
65 return this->visitExpressionPtr(expr);
66 }
67 }
68 }
69 if (expr->is<VariableReference>()) {
70 SkASSERT(!fDeadVariables.contains(expr->as<VariableReference>().variable()));
71 }
72 return INHERITED::visitExpressionPtr(expr);
73 }
74
75 bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
76 if (stmt->is<VarDeclaration>()) {
77 VarDeclaration& varDecl = stmt->as<VarDeclaration>();
78 const Variable* var = varDecl.var();
79 ProgramUsage::VariableCounts* counts = fUsage->fVariableCounts.find(var);
80 SkASSERT(counts);
81 SkASSERT(counts->fVarExists);
82 if (CanEliminate(var, *counts)) {
83 fDeadVariables.add(var);
84 if (var->initialValue()) {
85 // The variable has an initial-value expression, which might have side
86 // effects. ExpressionStatement::Make will preserve side effects, but
87 // replaces pure expressions with Nop.
88 fUsage->remove(stmt.get());
89 stmt = ExpressionStatement::Make(fContext, std::move(varDecl.value()));
90 fUsage->add(stmt.get());
91 } else {
92 // The variable has no initial-value and can be cleanly eliminated.
93 fUsage->remove(stmt.get());
94 stmt = Nop::Make();
95 }
96 fMadeChanges = true;
97
98 // Re-process the newly cleaned-up statement. This lets us fully clean up
99 // gnarly assignments like `a = b = 123;` where both `a` and `b` are dead,
100 // or silly double-assignments like `a = a = 123;`.
101 return this->visitStatementPtr(stmt);
102 }
103 }
104
105 bool result = INHERITED::visitStatementPtr(stmt);
106
107 // If we eliminated an assignment above, we may have left behind an inert
108 // ExpressionStatement.
109 if (fAssignmentWasEliminated) {
110 fAssignmentWasEliminated = false;
111 if (stmt->is<ExpressionStatement>()) {
112 ExpressionStatement& exprStmt = stmt->as<ExpressionStatement>();
113 if (!Analysis::HasSideEffects(*exprStmt.expression())) {
114 // The expression-statement was inert; eliminate it entirely.
115 fUsage->remove(&exprStmt);
116 stmt = Nop::Make();
117 }
118 }
119 }
120
121 return result;
122 }
123
124 static bool CanEliminate(const Variable* var, const ProgramUsage::VariableCounts& counts) {
125 return counts.fVarExists && !counts.fRead && var->storage() == VariableStorage::kLocal;
126 }
127
128 bool fMadeChanges = false;
129 const Context& fContext;
130 ProgramUsage* fUsage;
131 SkTHashSet<const Variable*> fDeadVariables;
132 bool fAssignmentWasEliminated = false;
133
134 using INHERITED = ProgramWriter;
135 };
136
137 DeadLocalVariableEliminator visitor{context, usage};
138
139 for (auto& [var, counts] : usage->fVariableCounts) {
140 if (DeadLocalVariableEliminator::CanEliminate(var, counts)) {
141 // This program contains at least one dead local variable.
142 // Scan the program for any dead local variables and eliminate them all.
143 for (std::unique_ptr<ProgramElement>& pe : elements) {
144 if (pe->is<FunctionDefinition>()) {
145 visitor.visitProgramElement(*pe);
146 }
147 }
148 break;
149 }
150 }
151
152 return visitor.fMadeChanges;
153 }
154
EliminateDeadLocalVariables(const Context & context,Module & module,ProgramUsage * usage)155 bool Transform::EliminateDeadLocalVariables(const Context& context,
156 Module& module,
157 ProgramUsage* usage) {
158 return eliminate_dead_local_variables(context, SkSpan(module.fElements), usage);
159 }
160
EliminateDeadLocalVariables(Program & program)161 bool Transform::EliminateDeadLocalVariables(Program& program) {
162 return program.fConfig->fSettings.fRemoveDeadVariables
163 ? eliminate_dead_local_variables(*program.fContext,
164 SkSpan(program.fOwnedElements),
165 program.fUsage.get())
166 : false;
167 }
168
169 } // namespace SkSL
170