blob: 1bba3c7da48ee67a9346c4408fa8434cd8c573cb [file] [log] [blame]
John Bauman66b8ab22014-05-06 15:57:45 -04001//
2// Copyright (c) 2002-2012 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
Nicolas Capenscc863da2015-01-21 15:50:55 -05007#include "ValidateLimitations.h"
8#include "InfoSink.h"
9#include "InitializeParseContext.h"
10#include "ParseHelper.h"
John Bauman66b8ab22014-05-06 15:57:45 -040011
12namespace {
13bool IsLoopIndex(const TIntermSymbol* symbol, const TLoopStack& stack) {
14 for (TLoopStack::const_iterator i = stack.begin(); i != stack.end(); ++i) {
15 if (i->index.id == symbol->getId())
16 return true;
17 }
18 return false;
19}
20
21void MarkLoopForUnroll(const TIntermSymbol* symbol, TLoopStack& stack) {
22 for (TLoopStack::iterator i = stack.begin(); i != stack.end(); ++i) {
23 if (i->index.id == symbol->getId()) {
24 ASSERT(i->loop != NULL);
25 i->loop->setUnrollFlag(true);
26 return;
27 }
28 }
Nicolas Capens3713cd42015-06-22 10:41:54 -040029 UNREACHABLE(0);
John Bauman66b8ab22014-05-06 15:57:45 -040030}
31
32// Traverses a node to check if it represents a constant index expression.
33// Definition:
34// constant-index-expressions are a superset of constant-expressions.
35// Constant-index-expressions can include loop indices as defined in
36// GLSL ES 1.0 spec, Appendix A, section 4.
37// The following are constant-index-expressions:
38// - Constant expressions
39// - Loop indices as defined in section 4
40// - Expressions composed of both of the above
41class ValidateConstIndexExpr : public TIntermTraverser {
42public:
43 ValidateConstIndexExpr(const TLoopStack& stack)
44 : mValid(true), mLoopStack(stack) {}
45
46 // Returns true if the parsed node represents a constant index expression.
47 bool isValid() const { return mValid; }
48
49 virtual void visitSymbol(TIntermSymbol* symbol) {
50 // Only constants and loop indices are allowed in a
51 // constant index expression.
52 if (mValid) {
Nicolas Capens31ad2aa2015-02-26 13:14:27 -050053 mValid = (symbol->getQualifier() == EvqConstExpr) ||
John Bauman66b8ab22014-05-06 15:57:45 -040054 IsLoopIndex(symbol, mLoopStack);
55 }
56 }
57
58private:
59 bool mValid;
60 const TLoopStack& mLoopStack;
61};
62
63// Traverses a node to check if it uses a loop index.
64// If an int loop index is used in its body as a sampler array index,
65// mark the loop for unroll.
66class ValidateLoopIndexExpr : public TIntermTraverser {
67public:
68 ValidateLoopIndexExpr(TLoopStack& stack)
69 : mUsesFloatLoopIndex(false),
70 mUsesIntLoopIndex(false),
71 mLoopStack(stack) {}
72
73 bool usesFloatLoopIndex() const { return mUsesFloatLoopIndex; }
74 bool usesIntLoopIndex() const { return mUsesIntLoopIndex; }
75
76 virtual void visitSymbol(TIntermSymbol* symbol) {
77 if (IsLoopIndex(symbol, mLoopStack)) {
78 switch (symbol->getBasicType()) {
79 case EbtFloat:
80 mUsesFloatLoopIndex = true;
81 break;
Nicolas Capens3c20f802015-02-17 17:17:20 -050082 case EbtUInt:
83 mUsesIntLoopIndex = true;
84 MarkLoopForUnroll(symbol, mLoopStack);
85 break;
John Bauman66b8ab22014-05-06 15:57:45 -040086 case EbtInt:
87 mUsesIntLoopIndex = true;
88 MarkLoopForUnroll(symbol, mLoopStack);
89 break;
90 default:
Nicolas Capens3713cd42015-06-22 10:41:54 -040091 UNREACHABLE(symbol->getBasicType());
John Bauman66b8ab22014-05-06 15:57:45 -040092 }
93 }
94 }
95
96private:
97 bool mUsesFloatLoopIndex;
98 bool mUsesIntLoopIndex;
99 TLoopStack& mLoopStack;
100};
101} // namespace
102
Nicolas Capens08ca3c62015-02-13 16:06:45 -0500103ValidateLimitations::ValidateLimitations(GLenum shaderType,
John Bauman66b8ab22014-05-06 15:57:45 -0400104 TInfoSinkBase& sink)
105 : mShaderType(shaderType),
106 mSink(sink),
107 mNumErrors(0)
108{
109}
110
111bool ValidateLimitations::visitBinary(Visit, TIntermBinary* node)
112{
113 // Check if loop index is modified in the loop body.
114 validateOperation(node, node->getLeft());
115
116 // Check indexing.
117 switch (node->getOp()) {
118 case EOpIndexDirect:
119 validateIndexing(node);
120 break;
121 case EOpIndexIndirect:
122#if defined(__APPLE__)
123 // Loop unrolling is a work-around for a Mac Cg compiler bug where it
124 // crashes when a sampler array's index is also the loop index.
125 // Once Apple fixes this bug, we should remove the code in this CL.
126 // See http://codereview.appspot.com/4331048/.
127 if ((node->getLeft() != NULL) && (node->getRight() != NULL) &&
128 (node->getLeft()->getAsSymbolNode())) {
129 TIntermSymbol* symbol = node->getLeft()->getAsSymbolNode();
130 if (IsSampler(symbol->getBasicType()) && symbol->isArray()) {
131 ValidateLoopIndexExpr validate(mLoopStack);
132 node->getRight()->traverse(&validate);
133 if (validate.usesFloatLoopIndex()) {
134 error(node->getLine(),
135 "sampler array index is float loop index",
136 "for");
137 }
138 }
139 }
140#endif
141 validateIndexing(node);
142 break;
143 default: break;
144 }
145 return true;
146}
147
148bool ValidateLimitations::visitUnary(Visit, TIntermUnary* node)
149{
150 // Check if loop index is modified in the loop body.
151 validateOperation(node, node->getOperand());
152
153 return true;
154}
155
156bool ValidateLimitations::visitAggregate(Visit, TIntermAggregate* node)
157{
158 switch (node->getOp()) {
159 case EOpFunctionCall:
160 validateFunctionCall(node);
161 break;
162 default:
163 break;
164 }
165 return true;
166}
167
168bool ValidateLimitations::visitLoop(Visit, TIntermLoop* node)
169{
170 if (!validateLoopType(node))
171 return false;
172
173 TLoopInfo info;
174 memset(&info, 0, sizeof(TLoopInfo));
175 info.loop = node;
176 if (!validateForLoopHeader(node, &info))
177 return false;
178
179 TIntermNode* body = node->getBody();
180 if (body != NULL) {
181 mLoopStack.push_back(info);
182 body->traverse(this);
183 mLoopStack.pop_back();
184 }
185
186 // The loop is fully processed - no need to visit children.
187 return false;
188}
189
190void ValidateLimitations::error(TSourceLoc loc,
191 const char *reason, const char* token)
192{
193 mSink.prefix(EPrefixError);
194 mSink.location(loc);
195 mSink << "'" << token << "' : " << reason << "\n";
196 ++mNumErrors;
197}
198
199bool ValidateLimitations::withinLoopBody() const
200{
201 return !mLoopStack.empty();
202}
203
204bool ValidateLimitations::isLoopIndex(const TIntermSymbol* symbol) const
205{
206 return IsLoopIndex(symbol, mLoopStack);
207}
208
209bool ValidateLimitations::validateLoopType(TIntermLoop* node) {
210 TLoopType type = node->getType();
211 if (type == ELoopFor)
212 return true;
213
214 // Reject while and do-while loops.
215 error(node->getLine(),
216 "This type of loop is not allowed",
217 type == ELoopWhile ? "while" : "do");
218 return false;
219}
220
221bool ValidateLimitations::validateForLoopHeader(TIntermLoop* node,
222 TLoopInfo* info)
223{
224 ASSERT(node->getType() == ELoopFor);
225
226 //
227 // The for statement has the form:
228 // for ( init-declaration ; condition ; expression ) statement
229 //
230 if (!validateForLoopInit(node, info))
231 return false;
232 if (!validateForLoopCond(node, info))
233 return false;
234 if (!validateForLoopExpr(node, info))
235 return false;
236
237 return true;
238}
239
240bool ValidateLimitations::validateForLoopInit(TIntermLoop* node,
241 TLoopInfo* info)
242{
243 TIntermNode* init = node->getInit();
244 if (init == NULL) {
245 error(node->getLine(), "Missing init declaration", "for");
246 return false;
247 }
248
249 //
250 // init-declaration has the form:
251 // type-specifier identifier = constant-expression
252 //
253 TIntermAggregate* decl = init->getAsAggregate();
254 if ((decl == NULL) || (decl->getOp() != EOpDeclaration)) {
255 error(init->getLine(), "Invalid init declaration", "for");
256 return false;
257 }
258 // To keep things simple do not allow declaration list.
259 TIntermSequence& declSeq = decl->getSequence();
260 if (declSeq.size() != 1) {
261 error(decl->getLine(), "Invalid init declaration", "for");
262 return false;
263 }
264 TIntermBinary* declInit = declSeq[0]->getAsBinaryNode();
265 if ((declInit == NULL) || (declInit->getOp() != EOpInitialize)) {
266 error(decl->getLine(), "Invalid init declaration", "for");
267 return false;
268 }
269 TIntermSymbol* symbol = declInit->getLeft()->getAsSymbolNode();
270 if (symbol == NULL) {
271 error(declInit->getLine(), "Invalid init declaration", "for");
272 return false;
273 }
274 // The loop index has type int or float.
275 TBasicType type = symbol->getBasicType();
Alexis Hetu9085c8d2015-06-01 13:48:07 -0400276 if (!IsInteger(type) && (type != EbtFloat)) {
John Bauman66b8ab22014-05-06 15:57:45 -0400277 error(symbol->getLine(),
278 "Invalid type for loop index", getBasicString(type));
279 return false;
280 }
281 // The loop index is initialized with constant expression.
282 if (!isConstExpr(declInit->getRight())) {
283 error(declInit->getLine(),
284 "Loop index cannot be initialized with non-constant expression",
285 symbol->getSymbol().c_str());
286 return false;
287 }
288
289 info->index.id = symbol->getId();
290 return true;
291}
292
293bool ValidateLimitations::validateForLoopCond(TIntermLoop* node,
294 TLoopInfo* info)
295{
296 TIntermNode* cond = node->getCondition();
297 if (cond == NULL) {
298 error(node->getLine(), "Missing condition", "for");
299 return false;
300 }
301 //
302 // condition has the form:
303 // loop_index relational_operator constant_expression
304 //
305 TIntermBinary* binOp = cond->getAsBinaryNode();
306 if (binOp == NULL) {
307 error(node->getLine(), "Invalid condition", "for");
308 return false;
309 }
310 // Loop index should be to the left of relational operator.
311 TIntermSymbol* symbol = binOp->getLeft()->getAsSymbolNode();
312 if (symbol == NULL) {
313 error(binOp->getLine(), "Invalid condition", "for");
314 return false;
315 }
316 if (symbol->getId() != info->index.id) {
317 error(symbol->getLine(),
318 "Expected loop index", symbol->getSymbol().c_str());
319 return false;
320 }
321 // Relational operator is one of: > >= < <= == or !=.
322 switch (binOp->getOp()) {
323 case EOpEqual:
324 case EOpNotEqual:
325 case EOpLessThan:
326 case EOpGreaterThan:
327 case EOpLessThanEqual:
328 case EOpGreaterThanEqual:
329 break;
330 default:
331 error(binOp->getLine(),
332 "Invalid relational operator",
333 getOperatorString(binOp->getOp()));
334 break;
335 }
336 // Loop index must be compared with a constant.
337 if (!isConstExpr(binOp->getRight())) {
338 error(binOp->getLine(),
339 "Loop index cannot be compared with non-constant expression",
340 symbol->getSymbol().c_str());
341 return false;
342 }
343
344 return true;
345}
346
347bool ValidateLimitations::validateForLoopExpr(TIntermLoop* node,
348 TLoopInfo* info)
349{
350 TIntermNode* expr = node->getExpression();
351 if (expr == NULL) {
352 error(node->getLine(), "Missing expression", "for");
353 return false;
354 }
355
356 // for expression has one of the following forms:
357 // loop_index++
358 // loop_index--
359 // loop_index += constant_expression
360 // loop_index -= constant_expression
361 // ++loop_index
362 // --loop_index
363 // The last two forms are not specified in the spec, but I am assuming
364 // its an oversight.
365 TIntermUnary* unOp = expr->getAsUnaryNode();
366 TIntermBinary* binOp = unOp ? NULL : expr->getAsBinaryNode();
367
368 TOperator op = EOpNull;
369 TIntermSymbol* symbol = NULL;
370 if (unOp != NULL) {
371 op = unOp->getOp();
372 symbol = unOp->getOperand()->getAsSymbolNode();
373 } else if (binOp != NULL) {
374 op = binOp->getOp();
375 symbol = binOp->getLeft()->getAsSymbolNode();
376 }
377
378 // The operand must be loop index.
379 if (symbol == NULL) {
380 error(expr->getLine(), "Invalid expression", "for");
381 return false;
382 }
383 if (symbol->getId() != info->index.id) {
384 error(symbol->getLine(),
385 "Expected loop index", symbol->getSymbol().c_str());
386 return false;
387 }
388
389 // The operator is one of: ++ -- += -=.
390 switch (op) {
391 case EOpPostIncrement:
392 case EOpPostDecrement:
393 case EOpPreIncrement:
394 case EOpPreDecrement:
395 ASSERT((unOp != NULL) && (binOp == NULL));
396 break;
397 case EOpAddAssign:
398 case EOpSubAssign:
399 ASSERT((unOp == NULL) && (binOp != NULL));
400 break;
401 default:
402 error(expr->getLine(), "Invalid operator", getOperatorString(op));
403 return false;
404 }
405
406 // Loop index must be incremented/decremented with a constant.
407 if (binOp != NULL) {
408 if (!isConstExpr(binOp->getRight())) {
409 error(binOp->getLine(),
410 "Loop index cannot be modified by non-constant expression",
411 symbol->getSymbol().c_str());
412 return false;
413 }
414 }
415
416 return true;
417}
418
419bool ValidateLimitations::validateFunctionCall(TIntermAggregate* node)
420{
421 ASSERT(node->getOp() == EOpFunctionCall);
422
423 // If not within loop body, there is nothing to check.
424 if (!withinLoopBody())
425 return true;
426
427 // List of param indices for which loop indices are used as argument.
428 typedef std::vector<int> ParamIndex;
429 ParamIndex pIndex;
430 TIntermSequence& params = node->getSequence();
431 for (TIntermSequence::size_type i = 0; i < params.size(); ++i) {
432 TIntermSymbol* symbol = params[i]->getAsSymbolNode();
433 if (symbol && isLoopIndex(symbol))
434 pIndex.push_back(i);
435 }
436 // If none of the loop indices are used as arguments,
437 // there is nothing to check.
438 if (pIndex.empty())
439 return true;
440
441 bool valid = true;
Nicolas Capens978ddc52014-11-11 12:42:08 -0500442 TSymbolTable& symbolTable = GetGlobalParseContext()->symbolTable;
Alexis Hetu0a655842015-06-22 16:52:11 -0400443 TSymbol* symbol = symbolTable.find(node->getName(), GetGlobalParseContext()->getShaderVersion());
John Bauman66b8ab22014-05-06 15:57:45 -0400444 ASSERT(symbol && symbol->isFunction());
445 TFunction* function = static_cast<TFunction*>(symbol);
446 for (ParamIndex::const_iterator i = pIndex.begin();
447 i != pIndex.end(); ++i) {
448 const TParameter& param = function->getParam(*i);
449 TQualifier qual = param.type->getQualifier();
450 if ((qual == EvqOut) || (qual == EvqInOut)) {
451 error(params[*i]->getLine(),
452 "Loop index cannot be used as argument to a function out or inout parameter",
453 params[*i]->getAsSymbolNode()->getSymbol().c_str());
454 valid = false;
455 }
456 }
457
458 return valid;
459}
460
461bool ValidateLimitations::validateOperation(TIntermOperator* node,
462 TIntermNode* operand) {
463 // Check if loop index is modified in the loop body.
464 if (!withinLoopBody() || !node->modifiesState())
465 return true;
466
467 const TIntermSymbol* symbol = operand->getAsSymbolNode();
468 if (symbol && isLoopIndex(symbol)) {
469 error(node->getLine(),
470 "Loop index cannot be statically assigned to within the body of the loop",
471 symbol->getSymbol().c_str());
472 }
473 return true;
474}
475
476bool ValidateLimitations::isConstExpr(TIntermNode* node)
477{
478 ASSERT(node != NULL);
479 return node->getAsConstantUnion() != NULL;
480}
481
482bool ValidateLimitations::isConstIndexExpr(TIntermNode* node)
483{
484 ASSERT(node != NULL);
485
486 ValidateConstIndexExpr validate(mLoopStack);
487 node->traverse(&validate);
488 return validate.isValid();
489}
490
491bool ValidateLimitations::validateIndexing(TIntermBinary* node)
492{
493 ASSERT((node->getOp() == EOpIndexDirect) ||
494 (node->getOp() == EOpIndexIndirect));
495
496 bool valid = true;
497 TIntermTyped* index = node->getRight();
498 // The index expression must have integral type.
Nicolas Capens3c20f802015-02-17 17:17:20 -0500499 if (!index->isScalarInt()) {
John Bauman66b8ab22014-05-06 15:57:45 -0400500 error(index->getLine(),
501 "Index expression must have integral type",
502 index->getCompleteString().c_str());
503 valid = false;
504 }
505 // The index expession must be a constant-index-expression unless
506 // the operand is a uniform in a vertex shader.
507 TIntermTyped* operand = node->getLeft();
Nicolas Capens08ca3c62015-02-13 16:06:45 -0500508 bool skip = (mShaderType == GL_VERTEX_SHADER) &&
John Bauman66b8ab22014-05-06 15:57:45 -0400509 (operand->getQualifier() == EvqUniform);
510 if (!skip && !isConstIndexExpr(index)) {
511 error(index->getLine(), "Index expression must be constant", "[]");
512 valid = false;
513 }
514 return valid;
515}
516