xref: /aosp_15_r20/external/skia/src/sksl/transform/SkSLEliminateUnreachableCode.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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