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