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