1//
2// Copyright (c) 2002-2013 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
7#include "compiler/translator/tree_ops/InitializeVariables.h"
8
9#include "angle_gl.h"
10#include "common/debug.h"
11#include "compiler/translator/StaticType.h"
12#include "compiler/translator/SymbolTable.h"
13#include "compiler/translator/tree_util/FindMain.h"
14#include "compiler/translator/tree_util/IntermNode_util.h"
15#include "compiler/translator/tree_util/IntermTraverse.h"
16#include "compiler/translator/util.h"
17
18namespace sh
19{
20
21namespace
22{
23
24void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
25 bool canUseLoopsToInitialize,
26 bool highPrecisionSupported,
27 TIntermSequence *initSequenceOut,
28 TSymbolTable *symbolTable);
29
30void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
31 bool canUseLoopsToInitialize,
32 bool highPrecisionSupported,
33 TIntermSequence *initSequenceOut,
34 TSymbolTable *symbolTable);
35
36TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
37{
38 TIntermTyped *zero = CreateZeroNode(initializedNode->getType());
39 return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
40}
41
42void AddZeroInitSequence(const TIntermTyped *initializedNode,
43 bool canUseLoopsToInitialize,
44 bool highPrecisionSupported,
45 TIntermSequence *initSequenceOut,
46 TSymbolTable *symbolTable)
47{
48 if (initializedNode->isArray())
49 {
50 AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
51 initSequenceOut, symbolTable);
52 }
53 else if (initializedNode->getType().isStructureContainingArrays() ||
54 initializedNode->getType().isNamelessStruct())
55 {
56 AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
57 initSequenceOut, symbolTable);
58 }
59 else
60 {
61 initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode));
62 }
63}
64
65void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
66 bool canUseLoopsToInitialize,
67 bool highPrecisionSupported,
68 TIntermSequence *initSequenceOut,
69 TSymbolTable *symbolTable)
70{
71 ASSERT(initializedNode->getBasicType() == EbtStruct);
72 const TStructure *structType = initializedNode->getType().getStruct();
73 for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i)
74 {
75 TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct,
76 initializedNode->deepCopy(), CreateIndexNode(i));
77 // Structs can't be defined inside structs, so the type of a struct field can't be a
78 // nameless struct.
79 ASSERT(!element->getType().isNamelessStruct());
80 AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
81 initSequenceOut, symbolTable);
82 }
83}
84
85void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode,
86 bool canUseLoopsToInitialize,
87 bool highPrecisionSupported,
88 TIntermSequence *initSequenceOut,
89 TSymbolTable *symbolTable)
90{
91 for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i)
92 {
93 TIntermBinary *element =
94 new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
95 AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
96 initSequenceOut, symbolTable);
97 }
98}
99
100void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode,
101 bool highPrecisionSupported,
102 TIntermSequence *initSequenceOut,
103 TSymbolTable *symbolTable)
104{
105 ASSERT(initializedNode->isArray());
106 const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>();
107 const TType *highpIndexType = StaticType::Get<EbtInt, EbpHigh, EvqTemporary, 1, 1>();
108 TVariable *indexVariable =
109 CreateTempVariable(symbolTable, highPrecisionSupported ? highpIndexType : mediumpIndexType);
110
111 TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexVariable);
112 TIntermDeclaration *indexInit =
113 CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType()));
114 TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize());
115 TIntermBinary *indexSmallerThanSize =
116 new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
117 TIntermUnary *indexIncrement =
118 new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr);
119
120 TIntermBlock *forLoopBody = new TIntermBlock();
121 TIntermSequence *forLoopBodySeq = forLoopBody->getSequence();
122
123 TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(),
124 indexSymbolNode->deepCopy());
125 AddZeroInitSequence(element, true, highPrecisionSupported, forLoopBodySeq, symbolTable);
126
127 TIntermLoop *forLoop =
128 new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody);
129 initSequenceOut->push_back(forLoop);
130}
131
132void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
133 bool canUseLoopsToInitialize,
134 bool highPrecisionSupported,
135 TIntermSequence *initSequenceOut,
136 TSymbolTable *symbolTable)
137{
138 // The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which
139 // doesn't have array assignment. We'll do this either with a for loop or just a list of
140 // statements assigning to each array index. Note that it is important to have the array init in
141 // the right order to workaround http://crbug.com/709317
142 bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u ||
143 (initializedNode->getBasicType() != EbtStruct &&
144 !initializedNode->getType().isArrayOfArrays() &&
145 initializedNode->getOutermostArraySize() <= 3u);
146 if (initializedNode->getQualifier() == EvqFragData ||
147 initializedNode->getQualifier() == EvqFragmentOut || isSmallArray ||
148 !canUseLoopsToInitialize)
149 {
150 // Fragment outputs should not be indexed by non-constant indices.
151 // Also it doesn't make sense to use loops to initialize very small arrays.
152 AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize,
153 highPrecisionSupported, initSequenceOut, symbolTable);
154 }
155 else
156 {
157 AddArrayZeroInitForLoop(initializedNode, highPrecisionSupported, initSequenceOut,
158 symbolTable);
159 }
160}
161
162void InsertInitCode(TIntermSequence *mainBody,
163 const InitVariableList &variables,
164 TSymbolTable *symbolTable,
165 int shaderVersion,
166 const TExtensionBehavior &extensionBehavior,
167 bool canUseLoopsToInitialize,
168 bool highPrecisionSupported)
169{
170 for (const auto &var : variables)
171 {
172 // Note that tempVariableName will reference a short-lived char array here - that's fine
173 // since we're only using it to find symbols.
174 ImmutableString tempVariableName(var.name.c_str(), var.name.length());
175
176 TIntermTyped *initializedSymbol = nullptr;
177 if (var.isBuiltIn())
178 {
179 initializedSymbol =
180 ReferenceBuiltInVariable(tempVariableName, *symbolTable, shaderVersion);
181 if (initializedSymbol->getQualifier() == EvqFragData &&
182 !IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
183 {
184 // If GL_EXT_draw_buffers is disabled, only the 0th index of gl_FragData can be
185 // written to.
186 // TODO(oetuaho): This is a bit hacky and would be better to remove, if we came up
187 // with a good way to do it. Right now "gl_FragData" in symbol table is initialized
188 // to have the array size of MaxDrawBuffers, and the initialization happens before
189 // the shader sets the extensions it is using.
190 initializedSymbol =
191 new TIntermBinary(EOpIndexDirect, initializedSymbol, CreateIndexNode(0));
192 }
193 }
194 else
195 {
196 initializedSymbol = ReferenceGlobalVariable(tempVariableName, *symbolTable);
197 }
198 ASSERT(initializedSymbol != nullptr);
199
200 TIntermSequence *initCode = CreateInitCode(initializedSymbol, canUseLoopsToInitialize,
201 highPrecisionSupported, symbolTable);
202 mainBody->insert(mainBody->begin(), initCode->begin(), initCode->end());
203 }
204}
205
206class InitializeLocalsTraverser : public TIntermTraverser
207{
208 public:
209 InitializeLocalsTraverser(int shaderVersion,
210 TSymbolTable *symbolTable,
211 bool canUseLoopsToInitialize,
212 bool highPrecisionSupported)
213 : TIntermTraverser(true, false, false, symbolTable),
214 mShaderVersion(shaderVersion),
215 mCanUseLoopsToInitialize(canUseLoopsToInitialize),
216 mHighPrecisionSupported(highPrecisionSupported)
217 {}
218
219 protected:
220 bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
221 {
222 for (TIntermNode *declarator : *node->getSequence())
223 {
224 if (!mInGlobalScope && !declarator->getAsBinaryNode())
225 {
226 TIntermSymbol *symbol = declarator->getAsSymbolNode();
227 ASSERT(symbol);
228 if (symbol->variable().symbolType() == SymbolType::Empty)
229 {
230 continue;
231 }
232
233 // Arrays may need to be initialized one element at a time, since ESSL 1.00 does not
234 // support array constructors or assigning arrays.
235 bool arrayConstructorUnavailable =
236 (symbol->isArray() || symbol->getType().isStructureContainingArrays()) &&
237 mShaderVersion == 100;
238 // Nameless struct constructors can't be referred to, so they also need to be
239 // initialized one element at a time.
240 // TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we
241 // could use an initializer. It could at least reduce code size for very large
242 // arrays, but could hurt runtime performance.
243 if (arrayConstructorUnavailable || symbol->getType().isNamelessStruct())
244 {
245 // SimplifyLoopConditions should have been run so the parent node of this node
246 // should not be a loop.
247 ASSERT(getParentNode()->getAsLoopNode() == nullptr);
248 // SeparateDeclarations should have already been run, so we don't need to worry
249 // about further declarators in this declaration depending on the effects of
250 // this declarator.
251 ASSERT(node->getSequence()->size() == 1);
252 insertStatementsInParentBlock(
253 TIntermSequence(), *CreateInitCode(symbol, mCanUseLoopsToInitialize,
254 mHighPrecisionSupported, mSymbolTable));
255 }
256 else
257 {
258 TIntermBinary *init =
259 new TIntermBinary(EOpInitialize, symbol, CreateZeroNode(symbol->getType()));
260 queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD);
261 }
262 }
263 }
264 return false;
265 }
266
267 private:
268 int mShaderVersion;
269 bool mCanUseLoopsToInitialize;
270 bool mHighPrecisionSupported;
271};
272
273} // namespace
274
275TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol,
276 bool canUseLoopsToInitialize,
277 bool highPrecisionSupported,
278 TSymbolTable *symbolTable)
279{
280 TIntermSequence *initCode = new TIntermSequence();
281 AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported,
282 initCode, symbolTable);
283 return initCode;
284}
285
286void InitializeUninitializedLocals(TIntermBlock *root,
287 int shaderVersion,
288 bool canUseLoopsToInitialize,
289 bool highPrecisionSupported,
290 TSymbolTable *symbolTable)
291{
292 InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize,
293 highPrecisionSupported);
294 root->traverse(&traverser);
295 traverser.updateTree();
296}
297
298void InitializeVariables(TIntermBlock *root,
299 const InitVariableList &vars,
300 TSymbolTable *symbolTable,
301 int shaderVersion,
302 const TExtensionBehavior &extensionBehavior,
303 bool canUseLoopsToInitialize,
304 bool highPrecisionSupported)
305{
306 TIntermBlock *body = FindMainBody(root);
307 InsertInitCode(body->getSequence(), vars, symbolTable, shaderVersion, extensionBehavior,
308 canUseLoopsToInitialize, highPrecisionSupported);
309}
310
311} // namespace sh
312