| // Copyright 2016 The SwiftShader Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "ValidateLimitations.h" |
| #include "InfoSink.h" |
| #include "InitializeParseContext.h" |
| #include "ParseHelper.h" |
| |
| namespace { |
| bool IsLoopIndex(const TIntermSymbol* symbol, const TLoopStack& stack) { |
| for (TLoopStack::const_iterator i = stack.begin(); i != stack.end(); ++i) { |
| if (i->index.id == symbol->getId()) |
| return true; |
| } |
| return false; |
| } |
| |
| void MarkLoopForUnroll(const TIntermSymbol* symbol, TLoopStack& stack) { |
| for (TLoopStack::iterator i = stack.begin(); i != stack.end(); ++i) { |
| if (i->index.id == symbol->getId()) { |
| ASSERT(i->loop); |
| i->loop->setUnrollFlag(true); |
| return; |
| } |
| } |
| UNREACHABLE(0); |
| } |
| |
| // Traverses a node to check if it represents a constant index expression. |
| // Definition: |
| // constant-index-expressions are a superset of constant-expressions. |
| // Constant-index-expressions can include loop indices as defined in |
| // GLSL ES 1.0 spec, Appendix A, section 4. |
| // The following are constant-index-expressions: |
| // - Constant expressions |
| // - Loop indices as defined in section 4 |
| // - Expressions composed of both of the above |
| class ValidateConstIndexExpr : public TIntermTraverser { |
| public: |
| ValidateConstIndexExpr(const TLoopStack& stack) |
| : mValid(true), mLoopStack(stack) {} |
| |
| // Returns true if the parsed node represents a constant index expression. |
| bool isValid() const { return mValid; } |
| |
| virtual void visitSymbol(TIntermSymbol* symbol) { |
| // Only constants and loop indices are allowed in a |
| // constant index expression. |
| if (mValid) { |
| mValid = (symbol->getQualifier() == EvqConstExpr) || |
| IsLoopIndex(symbol, mLoopStack); |
| } |
| } |
| |
| private: |
| bool mValid; |
| const TLoopStack& mLoopStack; |
| }; |
| |
| // Traverses a node to check if it uses a loop index. |
| // If an int loop index is used in its body as a sampler array index, |
| // mark the loop for unroll. |
| class ValidateLoopIndexExpr : public TIntermTraverser { |
| public: |
| ValidateLoopIndexExpr(TLoopStack& stack) |
| : mUsesFloatLoopIndex(false), |
| mUsesIntLoopIndex(false), |
| mLoopStack(stack) {} |
| |
| bool usesFloatLoopIndex() const { return mUsesFloatLoopIndex; } |
| bool usesIntLoopIndex() const { return mUsesIntLoopIndex; } |
| |
| virtual void visitSymbol(TIntermSymbol* symbol) { |
| if (IsLoopIndex(symbol, mLoopStack)) { |
| switch (symbol->getBasicType()) { |
| case EbtFloat: |
| mUsesFloatLoopIndex = true; |
| break; |
| case EbtUInt: |
| mUsesIntLoopIndex = true; |
| MarkLoopForUnroll(symbol, mLoopStack); |
| break; |
| case EbtInt: |
| mUsesIntLoopIndex = true; |
| MarkLoopForUnroll(symbol, mLoopStack); |
| break; |
| default: |
| UNREACHABLE(symbol->getBasicType()); |
| } |
| } |
| } |
| |
| private: |
| bool mUsesFloatLoopIndex; |
| bool mUsesIntLoopIndex; |
| TLoopStack& mLoopStack; |
| }; |
| } // namespace |
| |
| ValidateLimitations::ValidateLimitations(GLenum shaderType, |
| TInfoSinkBase& sink) |
| : mShaderType(shaderType), |
| mSink(sink), |
| mNumErrors(0) |
| { |
| } |
| |
| bool ValidateLimitations::visitBinary(Visit, TIntermBinary* node) |
| { |
| // Check if loop index is modified in the loop body. |
| validateOperation(node, node->getLeft()); |
| |
| // Check indexing. |
| switch (node->getOp()) { |
| case EOpIndexDirect: |
| validateIndexing(node); |
| break; |
| case EOpIndexIndirect: |
| validateIndexing(node); |
| break; |
| default: break; |
| } |
| return true; |
| } |
| |
| bool ValidateLimitations::visitUnary(Visit, TIntermUnary* node) |
| { |
| // Check if loop index is modified in the loop body. |
| validateOperation(node, node->getOperand()); |
| |
| return true; |
| } |
| |
| bool ValidateLimitations::visitAggregate(Visit, TIntermAggregate* node) |
| { |
| switch (node->getOp()) { |
| case EOpFunctionCall: |
| validateFunctionCall(node); |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| bool ValidateLimitations::visitLoop(Visit, TIntermLoop* node) |
| { |
| if (!validateLoopType(node)) |
| return false; |
| |
| TLoopInfo info; |
| memset(&info, 0, sizeof(TLoopInfo)); |
| info.loop = node; |
| if (!validateForLoopHeader(node, &info)) |
| return false; |
| |
| TIntermNode* body = node->getBody(); |
| if (body) { |
| mLoopStack.push_back(info); |
| body->traverse(this); |
| mLoopStack.pop_back(); |
| } |
| |
| // The loop is fully processed - no need to visit children. |
| return false; |
| } |
| |
| void ValidateLimitations::error(TSourceLoc loc, |
| const char *reason, const char* token) |
| { |
| mSink.prefix(EPrefixError); |
| mSink.location(loc); |
| mSink << "'" << token << "' : " << reason << "\n"; |
| ++mNumErrors; |
| } |
| |
| bool ValidateLimitations::withinLoopBody() const |
| { |
| return !mLoopStack.empty(); |
| } |
| |
| bool ValidateLimitations::isLoopIndex(const TIntermSymbol* symbol) const |
| { |
| return IsLoopIndex(symbol, mLoopStack); |
| } |
| |
| bool ValidateLimitations::validateLoopType(TIntermLoop* node) { |
| TLoopType type = node->getType(); |
| if (type == ELoopFor) |
| return true; |
| |
| // Reject while and do-while loops. |
| error(node->getLine(), |
| "This type of loop is not allowed", |
| type == ELoopWhile ? "while" : "do"); |
| return false; |
| } |
| |
| bool ValidateLimitations::validateForLoopHeader(TIntermLoop* node, |
| TLoopInfo* info) |
| { |
| ASSERT(node->getType() == ELoopFor); |
| |
| // |
| // The for statement has the form: |
| // for ( init-declaration ; condition ; expression ) statement |
| // |
| if (!validateForLoopInit(node, info)) |
| return false; |
| if (!validateForLoopCond(node, info)) |
| return false; |
| if (!validateForLoopExpr(node, info)) |
| return false; |
| |
| return true; |
| } |
| |
| bool ValidateLimitations::validateForLoopInit(TIntermLoop* node, |
| TLoopInfo* info) |
| { |
| TIntermNode* init = node->getInit(); |
| if (!init) { |
| error(node->getLine(), "Missing init declaration", "for"); |
| return false; |
| } |
| |
| // |
| // init-declaration has the form: |
| // type-specifier identifier = constant-expression |
| // |
| TIntermAggregate* decl = init->getAsAggregate(); |
| if (!decl || (decl->getOp() != EOpDeclaration)) { |
| error(init->getLine(), "Invalid init declaration", "for"); |
| return false; |
| } |
| // To keep things simple do not allow declaration list. |
| TIntermSequence& declSeq = decl->getSequence(); |
| if (declSeq.size() != 1) { |
| error(decl->getLine(), "Invalid init declaration", "for"); |
| return false; |
| } |
| TIntermBinary* declInit = declSeq[0]->getAsBinaryNode(); |
| if (!declInit || (declInit->getOp() != EOpInitialize)) { |
| error(decl->getLine(), "Invalid init declaration", "for"); |
| return false; |
| } |
| TIntermSymbol* symbol = declInit->getLeft()->getAsSymbolNode(); |
| if (!symbol) { |
| error(declInit->getLine(), "Invalid init declaration", "for"); |
| return false; |
| } |
| // The loop index has type int or float. |
| TBasicType type = symbol->getBasicType(); |
| if (!IsInteger(type) && (type != EbtFloat)) { |
| error(symbol->getLine(), |
| "Invalid type for loop index", getBasicString(type)); |
| return false; |
| } |
| // The loop index is initialized with constant expression. |
| if (!isConstExpr(declInit->getRight())) { |
| error(declInit->getLine(), |
| "Loop index cannot be initialized with non-constant expression", |
| symbol->getSymbol().c_str()); |
| return false; |
| } |
| |
| info->index.id = symbol->getId(); |
| return true; |
| } |
| |
| bool ValidateLimitations::validateForLoopCond(TIntermLoop* node, |
| TLoopInfo* info) |
| { |
| TIntermNode* cond = node->getCondition(); |
| if (!cond) { |
| error(node->getLine(), "Missing condition", "for"); |
| return false; |
| } |
| // |
| // condition has the form: |
| // loop_index relational_operator constant_expression |
| // |
| TIntermBinary* binOp = cond->getAsBinaryNode(); |
| if (!binOp) { |
| error(node->getLine(), "Invalid condition", "for"); |
| return false; |
| } |
| // Loop index should be to the left of relational operator. |
| TIntermSymbol* symbol = binOp->getLeft()->getAsSymbolNode(); |
| if (!symbol) { |
| error(binOp->getLine(), "Invalid condition", "for"); |
| return false; |
| } |
| if (symbol->getId() != info->index.id) { |
| error(symbol->getLine(), |
| "Expected loop index", symbol->getSymbol().c_str()); |
| return false; |
| } |
| // Relational operator is one of: > >= < <= == or !=. |
| switch (binOp->getOp()) { |
| case EOpEqual: |
| case EOpNotEqual: |
| case EOpLessThan: |
| case EOpGreaterThan: |
| case EOpLessThanEqual: |
| case EOpGreaterThanEqual: |
| break; |
| default: |
| error(binOp->getLine(), |
| "Invalid relational operator", |
| getOperatorString(binOp->getOp())); |
| break; |
| } |
| // Loop index must be compared with a constant. |
| if (!isConstExpr(binOp->getRight())) { |
| error(binOp->getLine(), |
| "Loop index cannot be compared with non-constant expression", |
| symbol->getSymbol().c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ValidateLimitations::validateForLoopExpr(TIntermLoop* node, |
| TLoopInfo* info) |
| { |
| TIntermNode* expr = node->getExpression(); |
| if (!expr) { |
| error(node->getLine(), "Missing expression", "for"); |
| return false; |
| } |
| |
| // for expression has one of the following forms: |
| // loop_index++ |
| // loop_index-- |
| // loop_index += constant_expression |
| // loop_index -= constant_expression |
| // ++loop_index |
| // --loop_index |
| // The last two forms are not specified in the spec, but I am assuming |
| // its an oversight. |
| TIntermUnary* unOp = expr->getAsUnaryNode(); |
| TIntermBinary* binOp = unOp ? nullptr : expr->getAsBinaryNode(); |
| |
| TOperator op = EOpNull; |
| TIntermSymbol* symbol = nullptr; |
| if (unOp) { |
| op = unOp->getOp(); |
| symbol = unOp->getOperand()->getAsSymbolNode(); |
| } else if (binOp) { |
| op = binOp->getOp(); |
| symbol = binOp->getLeft()->getAsSymbolNode(); |
| } |
| |
| // The operand must be loop index. |
| if (!symbol) { |
| error(expr->getLine(), "Invalid expression", "for"); |
| return false; |
| } |
| if (symbol->getId() != info->index.id) { |
| error(symbol->getLine(), |
| "Expected loop index", symbol->getSymbol().c_str()); |
| return false; |
| } |
| |
| // The operator is one of: ++ -- += -=. |
| switch (op) { |
| case EOpPostIncrement: |
| case EOpPostDecrement: |
| case EOpPreIncrement: |
| case EOpPreDecrement: |
| ASSERT((unOp != NULL) && (binOp == NULL)); |
| break; |
| case EOpAddAssign: |
| case EOpSubAssign: |
| ASSERT((unOp == NULL) && (binOp != NULL)); |
| break; |
| default: |
| error(expr->getLine(), "Invalid operator", getOperatorString(op)); |
| return false; |
| } |
| |
| // Loop index must be incremented/decremented with a constant. |
| if (binOp != NULL) { |
| if (!isConstExpr(binOp->getRight())) { |
| error(binOp->getLine(), |
| "Loop index cannot be modified by non-constant expression", |
| symbol->getSymbol().c_str()); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool ValidateLimitations::validateFunctionCall(TIntermAggregate* node) |
| { |
| ASSERT(node->getOp() == EOpFunctionCall); |
| |
| // If not within loop body, there is nothing to check. |
| if (!withinLoopBody()) |
| return true; |
| |
| // List of param indices for which loop indices are used as argument. |
| typedef std::vector<int> ParamIndex; |
| ParamIndex pIndex; |
| TIntermSequence& params = node->getSequence(); |
| for (TIntermSequence::size_type i = 0; i < params.size(); ++i) { |
| TIntermSymbol* symbol = params[i]->getAsSymbolNode(); |
| if (symbol && isLoopIndex(symbol)) |
| pIndex.push_back(i); |
| } |
| // If none of the loop indices are used as arguments, |
| // there is nothing to check. |
| if (pIndex.empty()) |
| return true; |
| |
| bool valid = true; |
| TSymbolTable& symbolTable = GetGlobalParseContext()->symbolTable; |
| TSymbol* symbol = symbolTable.find(node->getName(), GetGlobalParseContext()->getShaderVersion()); |
| ASSERT(symbol && symbol->isFunction()); |
| TFunction* function = static_cast<TFunction*>(symbol); |
| for (ParamIndex::const_iterator i = pIndex.begin(); |
| i != pIndex.end(); ++i) { |
| const TParameter& param = function->getParam(*i); |
| TQualifier qual = param.type->getQualifier(); |
| if ((qual == EvqOut) || (qual == EvqInOut)) { |
| error(params[*i]->getLine(), |
| "Loop index cannot be used as argument to a function out or inout parameter", |
| params[*i]->getAsSymbolNode()->getSymbol().c_str()); |
| valid = false; |
| } |
| } |
| |
| return valid; |
| } |
| |
| bool ValidateLimitations::validateOperation(TIntermOperator* node, |
| TIntermNode* operand) { |
| // Check if loop index is modified in the loop body. |
| if (!withinLoopBody() || !node->modifiesState()) |
| return true; |
| |
| const TIntermSymbol* symbol = operand->getAsSymbolNode(); |
| if (symbol && isLoopIndex(symbol)) { |
| error(node->getLine(), |
| "Loop index cannot be statically assigned to within the body of the loop", |
| symbol->getSymbol().c_str()); |
| } |
| return true; |
| } |
| |
| bool ValidateLimitations::isConstExpr(TIntermNode* node) |
| { |
| ASSERT(node); |
| return node->getAsConstantUnion() != nullptr; |
| } |
| |
| bool ValidateLimitations::isConstIndexExpr(TIntermNode* node) |
| { |
| ASSERT(node); |
| |
| ValidateConstIndexExpr validate(mLoopStack); |
| node->traverse(&validate); |
| return validate.isValid(); |
| } |
| |
| bool ValidateLimitations::validateIndexing(TIntermBinary* node) |
| { |
| ASSERT((node->getOp() == EOpIndexDirect) || |
| (node->getOp() == EOpIndexIndirect)); |
| |
| bool valid = true; |
| TIntermTyped* index = node->getRight(); |
| // The index expression must have integral type. |
| if (!index->isScalarInt()) { |
| error(index->getLine(), |
| "Index expression must have integral type", |
| index->getCompleteString().c_str()); |
| valid = false; |
| } |
| // The index expession must be a constant-index-expression unless |
| // the operand is a uniform in a vertex shader. |
| TIntermTyped* operand = node->getLeft(); |
| bool skip = (mShaderType == GL_VERTEX_SHADER) && |
| (operand->getQualifier() == EvqUniform); |
| if (!skip && !isConstIndexExpr(index)) { |
| error(index->getLine(), "Index expression must be constant", "[]"); |
| valid = false; |
| } |
| return valid; |
| } |
| |