xref: /aosp_15_r20/external/angle/src/compiler/translator/tree_ops/PruneInfiniteLoops.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 //
2 // Copyright 2024 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // PruneInfiniteLoops.cpp: The PruneInfiniteLoops function prunes:
7 //
8 // 1. while (true) { ... }
9 //
10 // 2. bool variable = true; /* variable is never accessed */
11 //    while (variable) { ... }
12 //
13 // In all cases, the loop must not have EOpBreak or EOpReturn inside to be allowed to prune.
14 //
15 // In all cases, for (...; condition; ...) is treated the same as while (condition).
16 //
17 // It quickly gets error-prone when trying to detect more complicated cases.  For example, it's
18 // temping to reject any |while (expression involving variable with no side effects)| because that's
19 // either while(true) or while(false), which is prune-able either way.  That detects loops like
20 // while(variable == false), while(variable + 2 != 4).  But for example
21 // while(coherent_buffer[variable]) may indeed not result in an infinite loop.  For now, we stick to
22 // the basic case.
23 
24 #include "compiler/translator/tree_ops/PruneInfiniteLoops.h"
25 
26 #include "compiler/translator/Symbol.h"
27 #include "compiler/translator/tree_util/IntermTraverse.h"
28 
29 #include <stack>
30 
31 namespace sh
32 {
33 
34 namespace
35 {
36 using VariableSet = TUnorderedSet<const TVariable *>;
37 
38 class FindConstantVariablesTraverser : public TIntermTraverser
39 {
40   public:
FindConstantVariablesTraverser(TSymbolTable * symbolTable)41     FindConstantVariablesTraverser(TSymbolTable *symbolTable)
42         : TIntermTraverser(true, false, false, symbolTable)
43     {}
44 
getConstVariables() const45     const VariableSet &getConstVariables() const { return mConstVariables; }
46 
47   private:
visitDeclaration(Visit,TIntermDeclaration * decl)48     bool visitDeclaration(Visit, TIntermDeclaration *decl) override
49     {
50         // Initially, assume every variable is a constant
51         TIntermSequence *sequence = decl->getSequence();
52         for (TIntermNode *node : *sequence)
53         {
54             TIntermSymbol *symbol = node->getAsSymbolNode();
55             if (symbol == nullptr)
56             {
57                 TIntermBinary *assign = node->getAsBinaryNode();
58                 ASSERT(assign != nullptr && assign->getOp() == EOpInitialize);
59 
60                 symbol = assign->getLeft()->getAsSymbolNode();
61                 ASSERT(symbol != nullptr);
62             }
63 
64             ASSERT(mConstVariables.find(&symbol->variable()) == mConstVariables.end());
65             mConstVariables.insert(&symbol->variable());
66         }
67 
68         return false;
69     }
70 
visitLoop(Visit visit,TIntermLoop * loop)71     bool visitLoop(Visit visit, TIntermLoop *loop) override
72     {
73         ASSERT(visit == PreVisit);
74 
75         // For simplicity, for now only consider conditions that are just |variable|.  In that case,
76         // the condition is not visited, so that `visitSymbol` doesn't consider this a write.
77         if (loop->getInit() != nullptr)
78         {
79             loop->getInit()->traverse(this);
80         }
81         if (loop->getExpression() != nullptr)
82         {
83             loop->getExpression()->traverse(this);
84         }
85         loop->getBody()->traverse(this);
86 
87         TIntermTyped *condition = loop->getCondition();
88         if (condition != nullptr &&
89             (condition->getAsSymbolNode() == nullptr || loop->getType() == ELoopDoWhile))
90         {
91             condition->traverse(this);
92         }
93 
94         return false;
95     }
96 
visitSymbol(TIntermSymbol * symbol)97     void visitSymbol(TIntermSymbol *symbol) override
98     {
99         // Assume write for simplicity.  AST makes it difficult to tell if this is read or write.
100         mConstVariables.erase(&symbol->variable());
101     }
102 
103     VariableSet mConstVariables;
104 };
105 
106 struct LoopStats
107 {
108     bool hasBreak  = false;
109     bool hasReturn = false;
110 };
111 
112 class PruneInfiniteLoopsTraverser : public TIntermTraverser
113 {
114   public:
PruneInfiniteLoopsTraverser(TSymbolTable * symbolTable,const VariableSet & constVariables)115     PruneInfiniteLoopsTraverser(TSymbolTable *symbolTable, const VariableSet &constVariables)
116         : TIntermTraverser(true, false, false, symbolTable),
117           mConstVariables(constVariables),
118           mAnyLoopsPruned(false)
119     {}
120 
anyLoopsPruned() const121     bool anyLoopsPruned() const { return mAnyLoopsPruned; }
122 
123   private:
124     bool visitLoop(Visit visit, TIntermLoop *loop) override;
125     bool visitSwitch(Visit visit, TIntermSwitch *node) override;
126     bool visitBranch(Visit visit, TIntermBranch *node) override;
127 
onScopeBegin()128     void onScopeBegin() { mLoopStats.push({}); }
129 
onScopeEnd()130     void onScopeEnd()
131     {
132         // Propagate |hasReturn| up the stack, it escapes every loop.
133         ASSERT(!mLoopStats.empty());
134         bool hasReturn = mLoopStats.top().hasReturn;
135         mLoopStats.pop();
136 
137         if (!mLoopStats.empty())
138         {
139             mLoopStats.top().hasReturn = mLoopStats.top().hasReturn || hasReturn;
140         }
141     }
142 
hasLoopEscape()143     bool hasLoopEscape()
144     {
145         ASSERT(!mLoopStats.empty());
146         return mLoopStats.top().hasBreak || mLoopStats.top().hasReturn;
147     }
148 
149     const VariableSet &mConstVariables;
150     std::stack<LoopStats> mLoopStats;
151     bool mAnyLoopsPruned;
152 };
153 
visitLoop(Visit visit,TIntermLoop * loop)154 bool PruneInfiniteLoopsTraverser::visitLoop(Visit visit, TIntermLoop *loop)
155 {
156     onScopeBegin();
157 
158     // Nothing in the init, condition or expression of loops can alter the control flow, just visit
159     // the body.
160     loop->getBody()->traverse(this);
161 
162     // Prune the loop if it has no breaks or returns, it's not do-while, and the condition is a
163     // constant variable.
164     TIntermTyped *condition              = loop->getCondition();
165     TIntermConstantUnion *constCondition = condition ? condition->getAsConstantUnion() : nullptr;
166     TIntermSymbol *conditionSymbol = condition ? loop->getCondition()->getAsSymbolNode() : nullptr;
167 
168     const bool isConditionConstant =
169         condition == nullptr || constCondition != nullptr ||
170         (conditionSymbol != nullptr &&
171          mConstVariables.find(&conditionSymbol->variable()) != mConstVariables.end());
172 
173     if (isConditionConstant && loop->getType() != ELoopDoWhile && !hasLoopEscape())
174     {
175         mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), loop, TIntermSequence{});
176         mAnyLoopsPruned = true;
177     }
178 
179     onScopeEnd();
180 
181     return false;
182 }
183 
visitSwitch(Visit visit,TIntermSwitch * node)184 bool PruneInfiniteLoopsTraverser::visitSwitch(Visit visit, TIntermSwitch *node)
185 {
186     // Insert a LoopStats node for switch, just so that breaks inside the switch are not considered
187     // loop breaks.
188     onScopeBegin();
189 
190     // Nothing in the switch expression that can alter the control flow, just visit the body.
191     node->getStatementList()->traverse(this);
192 
193     onScopeEnd();
194 
195     return false;
196 }
197 
visitBranch(Visit visit,TIntermBranch * node)198 bool PruneInfiniteLoopsTraverser::visitBranch(Visit visit, TIntermBranch *node)
199 {
200     if (!mLoopStats.empty())
201     {
202         switch (node->getFlowOp())
203         {
204             case EOpReturn:
205                 mLoopStats.top().hasReturn = true;
206                 break;
207             case EOpBreak:
208                 mLoopStats.top().hasBreak = true;
209                 break;
210             case EOpContinue:
211             case EOpKill:
212                 // Kill and continue don't let control flow escape from the loop
213                 break;
214             default:
215                 UNREACHABLE();
216         }
217     }
218 
219     // Only possible child is the value of a return statement, which has no significance.
220     return false;
221 }
222 }  // namespace
223 
PruneInfiniteLoops(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,bool * anyLoopsPruned)224 bool PruneInfiniteLoops(TCompiler *compiler,
225                         TIntermBlock *root,
226                         TSymbolTable *symbolTable,
227                         bool *anyLoopsPruned)
228 {
229     *anyLoopsPruned = false;
230 
231     FindConstantVariablesTraverser constVarTransverser(symbolTable);
232     root->traverse(&constVarTransverser);
233 
234     PruneInfiniteLoopsTraverser pruneTraverser(symbolTable,
235                                                constVarTransverser.getConstVariables());
236     root->traverse(&pruneTraverser);
237     *anyLoopsPruned = pruneTraverser.anyLoopsPruned();
238     return pruneTraverser.updateTree(compiler, root);
239 }
240 
241 }  // namespace sh
242