1 //
2 // Copyright 2002 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 // PruneNoOps.cpp: The PruneNoOps function prunes no-op statements.
7
8 #include "compiler/translator/tree_ops/PruneNoOps.h"
9
10 #include "compiler/translator/Symbol.h"
11 #include "compiler/translator/tree_util/IntermTraverse.h"
12
13 namespace sh
14 {
15
16 namespace
17 {
GetSwitchConstantAsUInt(const TConstantUnion * value)18 uint32_t GetSwitchConstantAsUInt(const TConstantUnion *value)
19 {
20 TConstantUnion asUInt;
21 if (value->getType() == EbtYuvCscStandardEXT)
22 {
23 asUInt.setUConst(value->getYuvCscStandardEXTConst());
24 }
25 else
26 {
27 bool valid = asUInt.cast(EbtUInt, *value);
28 ASSERT(valid);
29 }
30 return asUInt.getUConst();
31 }
32
IsNoOpSwitch(TIntermSwitch * node)33 bool IsNoOpSwitch(TIntermSwitch *node)
34 {
35 if (node == nullptr)
36 {
37 return false;
38 }
39
40 TIntermConstantUnion *expr = node->getInit()->getAsConstantUnion();
41 if (expr == nullptr)
42 {
43 return false;
44 }
45
46 const uint32_t exprValue = GetSwitchConstantAsUInt(expr->getConstantValue());
47
48 // See if any block matches the constant value
49 const TIntermSequence &statements = *node->getStatementList()->getSequence();
50
51 for (TIntermNode *statement : statements)
52 {
53 TIntermCase *caseLabel = statement->getAsCaseNode();
54 if (caseLabel == nullptr)
55 {
56 continue;
57 }
58
59 // Default matches everything, consider it not a no-op.
60 if (!caseLabel->hasCondition())
61 {
62 return false;
63 }
64
65 TIntermConstantUnion *condition = caseLabel->getCondition()->getAsConstantUnion();
66 ASSERT(condition != nullptr);
67
68 // If any case matches the value, it's not a no-op.
69 const uint32_t caseValue = GetSwitchConstantAsUInt(condition->getConstantValue());
70 if (caseValue == exprValue)
71 {
72 return false;
73 }
74 }
75
76 // No case matched the constant value the switch was used on, so the entire switch is a no-op.
77 return true;
78 }
79
IsNoOp(TIntermNode * node)80 bool IsNoOp(TIntermNode *node)
81 {
82 bool isEmptyDeclaration = node->getAsDeclarationNode() != nullptr &&
83 node->getAsDeclarationNode()->getSequence()->empty();
84 if (isEmptyDeclaration)
85 {
86 return true;
87 }
88
89 if (IsNoOpSwitch(node->getAsSwitchNode()))
90 {
91 return true;
92 }
93
94 if (node->getAsTyped() == nullptr || node->getAsFunctionPrototypeNode() != nullptr)
95 {
96 return false;
97 }
98
99 return !node->getAsTyped()->hasSideEffects();
100 }
101
102 class PruneNoOpsTraverser : private TIntermTraverser
103 {
104 public:
105 [[nodiscard]] static bool apply(TCompiler *compiler,
106 TIntermBlock *root,
107 TSymbolTable *symbolTable);
108
109 private:
110 PruneNoOpsTraverser(TSymbolTable *symbolTable);
111 bool visitDeclaration(Visit, TIntermDeclaration *node) override;
112 bool visitBlock(Visit visit, TIntermBlock *node) override;
113 bool visitLoop(Visit visit, TIntermLoop *loop) override;
114 bool visitBranch(Visit visit, TIntermBranch *node) override;
115
116 bool mIsBranchVisited = false;
117 };
118
apply(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable)119 bool PruneNoOpsTraverser::apply(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
120 {
121 PruneNoOpsTraverser prune(symbolTable);
122 root->traverse(&prune);
123 return prune.updateTree(compiler, root);
124 }
125
PruneNoOpsTraverser(TSymbolTable * symbolTable)126 PruneNoOpsTraverser::PruneNoOpsTraverser(TSymbolTable *symbolTable)
127 : TIntermTraverser(true, true, true, symbolTable)
128 {}
129
visitDeclaration(Visit visit,TIntermDeclaration * node)130 bool PruneNoOpsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
131 {
132 if (visit != PreVisit)
133 {
134 return true;
135 }
136
137 TIntermSequence *sequence = node->getSequence();
138 if (sequence->size() >= 1)
139 {
140 TIntermSymbol *declaratorSymbol = sequence->front()->getAsSymbolNode();
141 // Prune declarations without a variable name, unless it's an interface block declaration.
142 if (declaratorSymbol != nullptr &&
143 declaratorSymbol->variable().symbolType() == SymbolType::Empty &&
144 !declaratorSymbol->isInterfaceBlock())
145 {
146 if (sequence->size() > 1)
147 {
148 // Generate a replacement that will remove the empty declarator in the beginning of
149 // a declarator list. Example of a declaration that will be changed:
150 // float, a;
151 // will be changed to
152 // float a;
153 // This applies also to struct declarations.
154 TIntermSequence emptyReplacement;
155 mMultiReplacements.emplace_back(node, declaratorSymbol,
156 std::move(emptyReplacement));
157 }
158 else if (declaratorSymbol->getBasicType() != EbtStruct)
159 {
160 // If there are entirely empty non-struct declarations, they result in
161 // TIntermDeclaration nodes without any children in the parsing stage. These are
162 // handled in visitBlock and visitLoop.
163 UNREACHABLE();
164 }
165 else if (declaratorSymbol->getQualifier() != EvqGlobal &&
166 declaratorSymbol->getQualifier() != EvqTemporary)
167 {
168 // Single struct declarations may just declare the struct type and no variables, so
169 // they should not be pruned. Here we handle an empty struct declaration with a
170 // qualifier, for example like this:
171 // const struct a { int i; };
172 // NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so we
173 // convert the declaration to a regular struct declaration. This is okay, since ESSL
174 // 1.00 spec section 4.1.8 says about structs that "The optional qualifiers only
175 // apply to any declarators, and are not part of the type being defined for name."
176
177 // Create a new variable to use in the declarator so that the variable and node
178 // types are kept consistent.
179 TType *type = new TType(declaratorSymbol->getType());
180 if (mInGlobalScope)
181 {
182 type->setQualifier(EvqGlobal);
183 }
184 else
185 {
186 type->setQualifier(EvqTemporary);
187 }
188 TVariable *variable =
189 new TVariable(mSymbolTable, kEmptyImmutableString, type, SymbolType::Empty);
190 queueReplacementWithParent(node, declaratorSymbol, new TIntermSymbol(variable),
191 OriginalNode::IS_DROPPED);
192 }
193 }
194 }
195 return false;
196 }
197
visitBlock(Visit visit,TIntermBlock * node)198 bool PruneNoOpsTraverser::visitBlock(Visit visit, TIntermBlock *node)
199 {
200 ASSERT(visit == PreVisit);
201
202 TIntermSequence &statements = *node->getSequence();
203
204 // Visit each statement in the block one by one. Once a branch is visited (break, continue,
205 // return or discard), drop the rest of the statements.
206 for (size_t statementIndex = 0; statementIndex < statements.size(); ++statementIndex)
207 {
208 TIntermNode *statement = statements[statementIndex];
209
210 // If the statement is a switch case label, stop pruning and continue visiting the children.
211 if (statement->getAsCaseNode() != nullptr)
212 {
213 mIsBranchVisited = false;
214 }
215
216 // If a branch is visited, prune the statement. If the statement is a no-op, also prune it.
217 if (mIsBranchVisited || IsNoOp(statement))
218 {
219 TIntermSequence emptyReplacement;
220 mMultiReplacements.emplace_back(node, statement, std::move(emptyReplacement));
221 continue;
222 }
223
224 // Visit the statement if not pruned.
225 statement->traverse(this);
226 }
227
228 // If the parent is a block and mIsBranchVisited is set, this is a nested block without any
229 // condition (like if, loop or switch), so the rest of the parent block should also be pruned.
230 // Otherwise the parent block should be unaffected.
231 if (mIsBranchVisited && getParentNode()->getAsBlock() == nullptr)
232 {
233 mIsBranchVisited = false;
234 }
235
236 return false;
237 }
238
visitLoop(Visit visit,TIntermLoop * loop)239 bool PruneNoOpsTraverser::visitLoop(Visit visit, TIntermLoop *loop)
240 {
241 if (visit != PreVisit)
242 {
243 return true;
244 }
245
246 TIntermTyped *expr = loop->getExpression();
247 if (expr != nullptr && IsNoOp(expr))
248 {
249 loop->setExpression(nullptr);
250 }
251 TIntermNode *init = loop->getInit();
252 if (init != nullptr && IsNoOp(init))
253 {
254 loop->setInit(nullptr);
255 }
256
257 return true;
258 }
259
visitBranch(Visit visit,TIntermBranch * node)260 bool PruneNoOpsTraverser::visitBranch(Visit visit, TIntermBranch *node)
261 {
262 ASSERT(visit == PreVisit);
263
264 mIsBranchVisited = true;
265
266 // Only possible child is the value of a return statement, which has nothing to prune.
267 return false;
268 }
269 } // namespace
270
PruneNoOps(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable)271 bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
272 {
273 return PruneNoOpsTraverser::apply(compiler, root, symbolTable);
274 }
275
276 } // namespace sh
277