1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2021 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSpan.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTArray.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLDefines.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLModule.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/analysis/SkSLProgramUsage.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLFunctionDefinition.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLIRNode.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLIfStatement.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLNop.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLProgram.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLProgramElement.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLStatement.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLSwitchCase.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/ir/SkSLSwitchStatement.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/transform/SkSLProgramWriter.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/transform/SkSLTransform.h"
25*c8dee2aaSAndroid Build Coastguard Worker
26*c8dee2aaSAndroid Build Coastguard Worker #include <memory>
27*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
28*c8dee2aaSAndroid Build Coastguard Worker
29*c8dee2aaSAndroid Build Coastguard Worker using namespace skia_private;
30*c8dee2aaSAndroid Build Coastguard Worker
31*c8dee2aaSAndroid Build Coastguard Worker namespace SkSL {
32*c8dee2aaSAndroid Build Coastguard Worker
33*c8dee2aaSAndroid Build Coastguard Worker class Expression;
34*c8dee2aaSAndroid Build Coastguard Worker
eliminate_unreachable_code(SkSpan<std::unique_ptr<ProgramElement>> elements,ProgramUsage * usage)35*c8dee2aaSAndroid Build Coastguard Worker static void eliminate_unreachable_code(SkSpan<std::unique_ptr<ProgramElement>> elements,
36*c8dee2aaSAndroid Build Coastguard Worker ProgramUsage* usage) {
37*c8dee2aaSAndroid Build Coastguard Worker class UnreachableCodeEliminator : public ProgramWriter {
38*c8dee2aaSAndroid Build Coastguard Worker public:
39*c8dee2aaSAndroid Build Coastguard Worker UnreachableCodeEliminator(ProgramUsage* usage) : fUsage(usage) {
40*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.push_back(false);
41*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
42*c8dee2aaSAndroid Build Coastguard Worker }
43*c8dee2aaSAndroid Build Coastguard Worker
44*c8dee2aaSAndroid Build Coastguard Worker bool visitExpressionPtr(std::unique_ptr<Expression>& expr) override {
45*c8dee2aaSAndroid Build Coastguard Worker // We don't need to look inside expressions at all.
46*c8dee2aaSAndroid Build Coastguard Worker return false;
47*c8dee2aaSAndroid Build Coastguard Worker }
48*c8dee2aaSAndroid Build Coastguard Worker
49*c8dee2aaSAndroid Build Coastguard Worker bool visitStatementPtr(std::unique_ptr<Statement>& stmt) override {
50*c8dee2aaSAndroid Build Coastguard Worker if (fFoundFunctionExit.back() || fFoundBlockExit.back()) {
51*c8dee2aaSAndroid Build Coastguard Worker // If we already found an exit in this section, anything beyond it is dead code.
52*c8dee2aaSAndroid Build Coastguard Worker if (!stmt->is<Nop>()) {
53*c8dee2aaSAndroid Build Coastguard Worker // Eliminate the dead statement by substituting a Nop.
54*c8dee2aaSAndroid Build Coastguard Worker fUsage->remove(stmt.get());
55*c8dee2aaSAndroid Build Coastguard Worker stmt = Nop::Make();
56*c8dee2aaSAndroid Build Coastguard Worker }
57*c8dee2aaSAndroid Build Coastguard Worker return false;
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker
60*c8dee2aaSAndroid Build Coastguard Worker switch (stmt->kind()) {
61*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kReturn:
62*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kDiscard:
63*c8dee2aaSAndroid Build Coastguard Worker // We found a function exit on this path.
64*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.back() = true;
65*c8dee2aaSAndroid Build Coastguard Worker break;
66*c8dee2aaSAndroid Build Coastguard Worker
67*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kBreak:
68*c8dee2aaSAndroid Build Coastguard Worker // A `break` statement can either be breaking out of a loop or terminating an
69*c8dee2aaSAndroid Build Coastguard Worker // individual switch case. We treat both cases the same way: they only apply
70*c8dee2aaSAndroid Build Coastguard Worker // to the statements associated with the parent statement (i.e. enclosing loop
71*c8dee2aaSAndroid Build Coastguard Worker // block / preceding case label).
72*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kContinue:
73*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.back() = true;
74*c8dee2aaSAndroid Build Coastguard Worker break;
75*c8dee2aaSAndroid Build Coastguard Worker
76*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kExpression:
77*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kNop:
78*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kVarDeclaration:
79*c8dee2aaSAndroid Build Coastguard Worker // These statements don't affect control flow.
80*c8dee2aaSAndroid Build Coastguard Worker break;
81*c8dee2aaSAndroid Build Coastguard Worker
82*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kBlock:
83*c8dee2aaSAndroid Build Coastguard Worker // Blocks are on the straight-line path and don't affect control flow.
84*c8dee2aaSAndroid Build Coastguard Worker return INHERITED::visitStatementPtr(stmt);
85*c8dee2aaSAndroid Build Coastguard Worker
86*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kDo: {
87*c8dee2aaSAndroid Build Coastguard Worker // Function-exits are allowed to propagate outside of a do-loop, because it
88*c8dee2aaSAndroid Build Coastguard Worker // always executes its body at least once.
89*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
90*c8dee2aaSAndroid Build Coastguard Worker bool result = INHERITED::visitStatementPtr(stmt);
91*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.pop_back();
92*c8dee2aaSAndroid Build Coastguard Worker return result;
93*c8dee2aaSAndroid Build Coastguard Worker }
94*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kFor: {
95*c8dee2aaSAndroid Build Coastguard Worker // Function-exits are not allowed to propagate out, because a for-loop or while-
96*c8dee2aaSAndroid Build Coastguard Worker // loop could potentially run zero times.
97*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.push_back(false);
98*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
99*c8dee2aaSAndroid Build Coastguard Worker bool result = INHERITED::visitStatementPtr(stmt);
100*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.pop_back();
101*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.pop_back();
102*c8dee2aaSAndroid Build Coastguard Worker return result;
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kIf: {
105*c8dee2aaSAndroid Build Coastguard Worker // This statement is conditional and encloses two inner sections of code.
106*c8dee2aaSAndroid Build Coastguard Worker // If both sides contain a function-exit or loop-exit, that exit is allowed to
107*c8dee2aaSAndroid Build Coastguard Worker // propagate out.
108*c8dee2aaSAndroid Build Coastguard Worker IfStatement& ifStmt = stmt->as<IfStatement>();
109*c8dee2aaSAndroid Build Coastguard Worker
110*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.push_back(false);
111*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
112*c8dee2aaSAndroid Build Coastguard Worker bool result = (ifStmt.ifTrue() && this->visitStatementPtr(ifStmt.ifTrue()));
113*c8dee2aaSAndroid Build Coastguard Worker bool foundFunctionExitOnTrue = fFoundFunctionExit.back();
114*c8dee2aaSAndroid Build Coastguard Worker bool foundLoopExitOnTrue = fFoundBlockExit.back();
115*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.pop_back();
116*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.pop_back();
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.push_back(false);
119*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
120*c8dee2aaSAndroid Build Coastguard Worker result |= (ifStmt.ifFalse() && this->visitStatementPtr(ifStmt.ifFalse()));
121*c8dee2aaSAndroid Build Coastguard Worker bool foundFunctionExitOnFalse = fFoundFunctionExit.back();
122*c8dee2aaSAndroid Build Coastguard Worker bool foundLoopExitOnFalse = fFoundBlockExit.back();
123*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.pop_back();
124*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.pop_back();
125*c8dee2aaSAndroid Build Coastguard Worker
126*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.back() |= foundFunctionExitOnTrue &&
127*c8dee2aaSAndroid Build Coastguard Worker foundFunctionExitOnFalse;
128*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.back() |= foundLoopExitOnTrue &&
129*c8dee2aaSAndroid Build Coastguard Worker foundLoopExitOnFalse;
130*c8dee2aaSAndroid Build Coastguard Worker return result;
131*c8dee2aaSAndroid Build Coastguard Worker }
132*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kSwitch: {
133*c8dee2aaSAndroid Build Coastguard Worker // In switch statements we consider unreachable code on a per-case basis.
134*c8dee2aaSAndroid Build Coastguard Worker SwitchStatement& sw = stmt->as<SwitchStatement>();
135*c8dee2aaSAndroid Build Coastguard Worker bool result = false;
136*c8dee2aaSAndroid Build Coastguard Worker
137*c8dee2aaSAndroid Build Coastguard Worker // Tracks whether we found at least one case that doesn't lead to a return
138*c8dee2aaSAndroid Build Coastguard Worker // statement (potentially via fallthrough).
139*c8dee2aaSAndroid Build Coastguard Worker bool foundCaseWithoutReturn = false;
140*c8dee2aaSAndroid Build Coastguard Worker bool hasDefault = false;
141*c8dee2aaSAndroid Build Coastguard Worker for (std::unique_ptr<Statement>& c : sw.cases()) {
142*c8dee2aaSAndroid Build Coastguard Worker // We eliminate unreachable code within the statements of the individual
143*c8dee2aaSAndroid Build Coastguard Worker // case. Breaks are not allowed to propagate outside the case statement
144*c8dee2aaSAndroid Build Coastguard Worker // itself. Function returns are allowed to propagate out only if all cases
145*c8dee2aaSAndroid Build Coastguard Worker // have a return AND one of the cases is default (so that we know at least
146*c8dee2aaSAndroid Build Coastguard Worker // one of the branches will be taken). This is similar to how we handle if
147*c8dee2aaSAndroid Build Coastguard Worker // statements above.
148*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.push_back(false);
149*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.push_back(false);
150*c8dee2aaSAndroid Build Coastguard Worker
151*c8dee2aaSAndroid Build Coastguard Worker SwitchCase& sc = c->as<SwitchCase>();
152*c8dee2aaSAndroid Build Coastguard Worker result |= this->visitStatementPtr(sc.statement());
153*c8dee2aaSAndroid Build Coastguard Worker
154*c8dee2aaSAndroid Build Coastguard Worker // When considering whether a case has a return we can propagate, we
155*c8dee2aaSAndroid Build Coastguard Worker // assume the following:
156*c8dee2aaSAndroid Build Coastguard Worker // 1. The default case is always placed last in a switch statement and
157*c8dee2aaSAndroid Build Coastguard Worker // it is the last possible label reachable via fallthrough. Thus if
158*c8dee2aaSAndroid Build Coastguard Worker // it does not contain a return statement, then we don't propagate a
159*c8dee2aaSAndroid Build Coastguard Worker // function return.
160*c8dee2aaSAndroid Build Coastguard Worker // 2. In all other cases we prevent the return from propagating only if
161*c8dee2aaSAndroid Build Coastguard Worker // we encounter a break statement. If no return or break is found,
162*c8dee2aaSAndroid Build Coastguard Worker // we defer the decision to the fallthrough case. We won't propagate
163*c8dee2aaSAndroid Build Coastguard Worker // a return unless we eventually encounter a default label.
164*c8dee2aaSAndroid Build Coastguard Worker //
165*c8dee2aaSAndroid Build Coastguard Worker // See resources/sksl/shared/SwitchWithEarlyReturn.sksl for test cases that
166*c8dee2aaSAndroid Build Coastguard Worker // exercise this.
167*c8dee2aaSAndroid Build Coastguard Worker if (sc.isDefault()) {
168*c8dee2aaSAndroid Build Coastguard Worker foundCaseWithoutReturn |= !fFoundFunctionExit.back();
169*c8dee2aaSAndroid Build Coastguard Worker hasDefault = true;
170*c8dee2aaSAndroid Build Coastguard Worker } else {
171*c8dee2aaSAndroid Build Coastguard Worker // We can only be sure that a case does not lead to a return if it
172*c8dee2aaSAndroid Build Coastguard Worker // doesn't fallthrough.
173*c8dee2aaSAndroid Build Coastguard Worker foundCaseWithoutReturn |=
174*c8dee2aaSAndroid Build Coastguard Worker (!fFoundFunctionExit.back() && fFoundBlockExit.back());
175*c8dee2aaSAndroid Build Coastguard Worker }
176*c8dee2aaSAndroid Build Coastguard Worker
177*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.pop_back();
178*c8dee2aaSAndroid Build Coastguard Worker fFoundBlockExit.pop_back();
179*c8dee2aaSAndroid Build Coastguard Worker }
180*c8dee2aaSAndroid Build Coastguard Worker
181*c8dee2aaSAndroid Build Coastguard Worker fFoundFunctionExit.back() |= !foundCaseWithoutReturn && hasDefault;
182*c8dee2aaSAndroid Build Coastguard Worker return result;
183*c8dee2aaSAndroid Build Coastguard Worker }
184*c8dee2aaSAndroid Build Coastguard Worker case Statement::Kind::kSwitchCase:
185*c8dee2aaSAndroid Build Coastguard Worker // We should never hit this case as switch cases are handled in the previous
186*c8dee2aaSAndroid Build Coastguard Worker // case.
187*c8dee2aaSAndroid Build Coastguard Worker SkUNREACHABLE;
188*c8dee2aaSAndroid Build Coastguard Worker }
189*c8dee2aaSAndroid Build Coastguard Worker
190*c8dee2aaSAndroid Build Coastguard Worker return false;
191*c8dee2aaSAndroid Build Coastguard Worker }
192*c8dee2aaSAndroid Build Coastguard Worker
193*c8dee2aaSAndroid Build Coastguard Worker ProgramUsage* fUsage;
194*c8dee2aaSAndroid Build Coastguard Worker STArray<32, bool> fFoundFunctionExit;
195*c8dee2aaSAndroid Build Coastguard Worker STArray<32, bool> fFoundBlockExit;
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Worker using INHERITED = ProgramWriter;
198*c8dee2aaSAndroid Build Coastguard Worker };
199*c8dee2aaSAndroid Build Coastguard Worker
200*c8dee2aaSAndroid Build Coastguard Worker for (std::unique_ptr<ProgramElement>& pe : elements) {
201*c8dee2aaSAndroid Build Coastguard Worker if (pe->is<FunctionDefinition>()) {
202*c8dee2aaSAndroid Build Coastguard Worker UnreachableCodeEliminator visitor{usage};
203*c8dee2aaSAndroid Build Coastguard Worker visitor.visitStatementPtr(pe->as<FunctionDefinition>().body());
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker }
206*c8dee2aaSAndroid Build Coastguard Worker }
207*c8dee2aaSAndroid Build Coastguard Worker
EliminateUnreachableCode(Module & module,ProgramUsage * usage)208*c8dee2aaSAndroid Build Coastguard Worker void Transform::EliminateUnreachableCode(Module& module, ProgramUsage* usage) {
209*c8dee2aaSAndroid Build Coastguard Worker return eliminate_unreachable_code(SkSpan(module.fElements), usage);
210*c8dee2aaSAndroid Build Coastguard Worker }
211*c8dee2aaSAndroid Build Coastguard Worker
EliminateUnreachableCode(Program & program)212*c8dee2aaSAndroid Build Coastguard Worker void Transform::EliminateUnreachableCode(Program& program) {
213*c8dee2aaSAndroid Build Coastguard Worker return eliminate_unreachable_code(SkSpan(program.fOwnedElements), program.fUsage.get());
214*c8dee2aaSAndroid Build Coastguard Worker }
215*c8dee2aaSAndroid Build Coastguard Worker
216*c8dee2aaSAndroid Build Coastguard Worker } // namespace SkSL
217