More ground work for Uniform blocks Moved some of the struct / indexing code from glslang.y to the ParserHelper class and prepared it for uniform blocks. Change-Id: I2d5d380f662f36f04d74783fd542c4b258d3f3a5 Reviewed-on: https://swiftshader-review.googlesource.com/3441 Tested-by: Alexis Hétu <sugoi@google.com> Reviewed-by: Nicolas Capens <capn@google.com>
diff --git a/src/OpenGL/compiler/ParseHelper.cpp b/src/OpenGL/compiler/ParseHelper.cpp index 22e38a7..2904cfd 100644 --- a/src/OpenGL/compiler/ParseHelper.cpp +++ b/src/OpenGL/compiler/ParseHelper.cpp
@@ -981,6 +981,26 @@ return false; } +bool TParseContext::functionCallLValueErrorCheck(const TFunction *fnCandidate, TIntermAggregate *aggregate) +{ + for(size_t i = 0; i < fnCandidate->getParamCount(); ++i) + { + TQualifier qual = fnCandidate->getParam(i).type->getQualifier(); + if(qual == EvqOut || qual == EvqInOut) + { + TIntermTyped *node = (aggregate->getSequence())[i]->getAsTyped(); + if(lValueErrorCheck(node->getLine(), "assign", node)) + { + error(node->getLine(), + "Constant value cannot be passed for 'out' or 'inout' parameters.", "Error"); + recover(); + return true; + } + } + } + return false; +} + bool TParseContext::supportsExtension(const char* extension) { const TExtensionBehavior& extbehavior = extensionBehavior(); @@ -1403,7 +1423,7 @@ // If there is an embedded/nested struct, it appropriately calls addConstStructNested or addConstStructFromAggr // function and returns the parse-tree with the values of the embedded/nested struct. // -TIntermTyped* TParseContext::addConstStruct(TString& identifier, TIntermTyped* node, TSourceLoc line) +TIntermTyped* TParseContext::addConstStruct(const TString& identifier, TIntermTyped* node, TSourceLoc line) { const TFieldList &fields = node->getType().getStruct()->fields(); TIntermTyped *typedNode; @@ -1433,13 +1453,394 @@ return typedNode; } +// +// Parse an array index expression +// +TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression, const TSourceLoc &location, TIntermTyped *indexExpression) +{ + TIntermTyped *indexedExpression = NULL; + + if(!baseExpression->isArray() && !baseExpression->isMatrix() && !baseExpression->isVector()) + { + if(baseExpression->getAsSymbolNode()) + { + error(location, " left of '[' is not of type array, matrix, or vector ", + baseExpression->getAsSymbolNode()->getSymbol().c_str()); + } + else + { + error(location, " left of '[' is not of type array, matrix, or vector ", "expression"); + } + recover(); + } + + TIntermConstantUnion *indexConstantUnion = indexExpression->getAsConstantUnion(); + + if(indexExpression->getQualifier() == EvqConstExpr && indexConstantUnion) + { + int index = indexConstantUnion->getIConst(0); + if(index < 0) + { + std::stringstream infoStream; + infoStream << index; + std::string info = infoStream.str(); + error(location, "negative index", info.c_str()); + recover(); + index = 0; + } + if(baseExpression->getType().getQualifier() == EvqConstExpr) + { + if(baseExpression->isArray()) + { + // constant folding for arrays + indexedExpression = addConstArrayNode(index, baseExpression, location); + } + else if(baseExpression->isVector()) + { + // constant folding for vectors + TVectorFields fields; + fields.num = 1; + fields.offsets[0] = index; // need to do it this way because v.xy sends fields integer array + indexedExpression = addConstVectorNode(fields, baseExpression, location); + } + else if(baseExpression->isMatrix()) + { + // constant folding for matrices + indexedExpression = addConstMatrixNode(index, baseExpression, location); + } + } + else + { + int safeIndex = -1; + + if(baseExpression->isArray()) + { + if(index >= baseExpression->getType().getArraySize()) + { + std::stringstream extraInfoStream; + extraInfoStream << "array index out of range '" << index << "'"; + std::string extraInfo = extraInfoStream.str(); + error(location, "", "[", extraInfo.c_str()); + recover(); + safeIndex = baseExpression->getType().getArraySize() - 1; + } + } + else if((baseExpression->isVector() || baseExpression->isMatrix()) && + baseExpression->getType().getNominalSize() <= index) + { + std::stringstream extraInfoStream; + extraInfoStream << "field selection out of range '" << index << "'"; + std::string extraInfo = extraInfoStream.str(); + error(location, "", "[", extraInfo.c_str()); + recover(); + safeIndex = baseExpression->getType().getNominalSize() - 1; + } + + // Don't modify the data of the previous constant union, because it can point + // to builtins, like gl_MaxDrawBuffers. Instead use a new sanitized object. + if(safeIndex != -1) + { + ConstantUnion *safeConstantUnion = new ConstantUnion(); + safeConstantUnion->setIConst(safeIndex); + indexConstantUnion->replaceConstantUnion(safeConstantUnion); + } + + indexedExpression = intermediate.addIndex(EOpIndexDirect, baseExpression, indexExpression, location); + } + } + else + { + if(baseExpression->isInterfaceBlock()) + { + error(location, "", + "[", "array indexes for interface blocks arrays must be constant integral expressions"); + recover(); + } + // FIXME + /* + else if(baseExpression->getQualifier() == EvqFragmentOut) + { + error(location, "", "[", "array indexes for fragment outputs must be constant integral expressions"); + recover(); + } + */ + + indexedExpression = intermediate.addIndex(EOpIndexIndirect, baseExpression, indexExpression, location); + } + + if(indexedExpression == 0) + { + ConstantUnion *unionArray = new ConstantUnion[1]; + unionArray->setFConst(0.0f); + indexedExpression = intermediate.addConstantUnion(unionArray, TType(EbtFloat, EbpHigh, EvqConstExpr), location); + } + else if(baseExpression->isArray()) + { + const TType &baseType = baseExpression->getType(); + if(baseType.getStruct()) + { + TType copyOfType(baseType.getStruct()); + indexedExpression->setType(copyOfType); + } + else if(baseType.isInterfaceBlock()) + { + TType copyOfType(baseType.getInterfaceBlock(), baseType.getQualifier(), baseType.getLayoutQualifier(), 0); + indexedExpression->setType(copyOfType); + } + else + { + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), + EvqTemporary, static_cast<unsigned char>(baseExpression->getNominalSize()), + static_cast<unsigned char>(baseExpression->getSecondarySize()))); + } + + if(baseExpression->getType().getQualifier() == EvqConstExpr) + { + indexedExpression->getTypePointer()->setQualifier(EvqConstExpr); + } + } + else if(baseExpression->isMatrix()) + { + TQualifier qualifier = baseExpression->getType().getQualifier() == EvqConstExpr ? EvqConstExpr : EvqTemporary; + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), + qualifier, static_cast<unsigned char>(baseExpression->getSecondarySize()))); + } + else if(baseExpression->isVector()) + { + TQualifier qualifier = baseExpression->getType().getQualifier() == EvqConstExpr ? EvqConstExpr : EvqTemporary; + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), qualifier)); + } + else + { + indexedExpression->setType(baseExpression->getType()); + } + + return indexedExpression; +} + +TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpression, const TSourceLoc &dotLocation, + const TString &fieldString, const TSourceLoc &fieldLocation) +{ + TIntermTyped *indexedExpression = NULL; + + if(baseExpression->isArray()) + { + error(fieldLocation, "cannot apply dot operator to an array", "."); + recover(); + } + + if(baseExpression->isVector()) + { + TVectorFields fields; + if(!parseVectorFields(fieldString, baseExpression->getNominalSize(), fields, fieldLocation)) + { + fields.num = 1; + fields.offsets[0] = 0; + recover(); + } + + if(baseExpression->getType().getQualifier() == EvqConstExpr) + { + // constant folding for vector fields + indexedExpression = addConstVectorNode(fields, baseExpression, fieldLocation); + if(indexedExpression == 0) + { + recover(); + indexedExpression = baseExpression; + } + else + { + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), + EvqConstExpr, (unsigned char)(fieldString).size())); + } + } + else + { + TString vectorString = fieldString; + TIntermTyped *index = intermediate.addSwizzle(fields, fieldLocation); + indexedExpression = intermediate.addIndex(EOpVectorSwizzle, baseExpression, index, dotLocation); + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), + EvqTemporary, (unsigned char)vectorString.size())); + } + } + else if(baseExpression->isMatrix()) + { + TMatrixFields fields; + if(!parseMatrixFields(fieldString, baseExpression->getNominalSize(), baseExpression->getSecondarySize(), fields, fieldLocation)) + { + fields.wholeRow = false; + fields.wholeCol = false; + fields.row = 0; + fields.col = 0; + recover(); + } + + if(fields.wholeRow || fields.wholeCol) + { + error(dotLocation, " non-scalar fields not implemented yet", "."); + recover(); + ConstantUnion *unionArray = new ConstantUnion[1]; + unionArray->setIConst(0); + TIntermTyped *index = intermediate.addConstantUnion(unionArray, TType(EbtInt, EbpUndefined, EvqConstExpr), + fieldLocation); + indexedExpression = intermediate.addIndex(EOpIndexDirect, baseExpression, index, dotLocation); + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision(), + EvqTemporary, static_cast<unsigned char>(baseExpression->getNominalSize()), + static_cast<unsigned char>(baseExpression->getSecondarySize()))); + } + else + { + ConstantUnion *unionArray = new ConstantUnion[1]; + unionArray->setIConst(fields.col * baseExpression->getSecondarySize() + fields.row); + TIntermTyped *index = intermediate.addConstantUnion(unionArray, TType(EbtInt, EbpUndefined, EvqConstExpr), + fieldLocation); + indexedExpression = intermediate.addIndex(EOpIndexDirect, baseExpression, index, dotLocation); + indexedExpression->setType(TType(baseExpression->getBasicType(), baseExpression->getPrecision())); + } + } + else if(baseExpression->getBasicType() == EbtStruct) + { + bool fieldFound = false; + const TFieldList &fields = baseExpression->getType().getStruct()->fields(); + if(fields.empty()) + { + error(dotLocation, "structure has no fields", "Internal Error"); + recover(); + indexedExpression = baseExpression; + } + else + { + unsigned int i; + for(i = 0; i < fields.size(); ++i) + { + if(fields[i]->name() == fieldString) + { + fieldFound = true; + break; + } + } + if(fieldFound) + { + if(baseExpression->getType().getQualifier() == EvqConstExpr) + { + indexedExpression = addConstStruct(fieldString, baseExpression, dotLocation); + if(indexedExpression == 0) + { + recover(); + indexedExpression = baseExpression; + } + else + { + indexedExpression->setType(*fields[i]->type()); + // change the qualifier of the return type, not of the structure field + // as the structure definition is shared between various structures. + indexedExpression->getTypePointer()->setQualifier(EvqConstExpr); + } + } + else + { + ConstantUnion *unionArray = new ConstantUnion[1]; + unionArray->setIConst(i); + TIntermTyped *index = intermediate.addConstantUnion(unionArray, *fields[i]->type(), fieldLocation); + indexedExpression = intermediate.addIndex(EOpIndexDirectStruct, baseExpression, index, dotLocation); + indexedExpression->setType(*fields[i]->type()); + } + } + else + { + error(dotLocation, " no such field in structure", fieldString.c_str()); + recover(); + indexedExpression = baseExpression; + } + } + } + else if(baseExpression->isInterfaceBlock()) + { + bool fieldFound = false; + const TFieldList &fields = baseExpression->getType().getInterfaceBlock()->fields(); + if(fields.empty()) + { + error(dotLocation, "interface block has no fields", "Internal Error"); + recover(); + indexedExpression = baseExpression; + } + else + { + unsigned int i; + for(i = 0; i < fields.size(); ++i) + { + if(fields[i]->name() == fieldString) + { + fieldFound = true; + break; + } + } + if(fieldFound) + { + ConstantUnion *unionArray = new ConstantUnion[1]; + unionArray->setIConst(i); + TIntermTyped *index = intermediate.addConstantUnion(unionArray, *fields[i]->type(), fieldLocation); + indexedExpression = intermediate.addIndex(EOpIndexDirectInterfaceBlock, baseExpression, index, + dotLocation); + indexedExpression->setType(*fields[i]->type()); + } + else + { + error(dotLocation, " no such field in interface block", fieldString.c_str()); + recover(); + indexedExpression = baseExpression; + } + } + } + else + { + if(shaderVersion < 300) + { + error(dotLocation, " field selection requires structure, vector, or matrix on left hand side", + fieldString.c_str()); + } + else + { + error(dotLocation, + " field selection requires structure, vector, matrix, or interface block on left hand side", + fieldString.c_str()); + } + recover(); + indexedExpression = baseExpression; + } + + return indexedExpression; +} + TLayoutQualifier TParseContext::parseLayoutQualifier(const TString &qualifierType, const TSourceLoc& qualifierTypeLine) { TLayoutQualifier qualifier; qualifier.location = -1; + qualifier.matrixPacking = EmpUnspecified; + qualifier.blockStorage = EbsUnspecified; - if (qualifierType == "location") + if(qualifierType == "shared") + { + qualifier.blockStorage = EbsShared; + } + else if(qualifierType == "packed") + { + qualifier.blockStorage = EbsPacked; + } + else if(qualifierType == "std140") + { + qualifier.blockStorage = EbsStd140; + } + else if(qualifierType == "row_major") + { + qualifier.matrixPacking = EmpRowMajor; + } + else if(qualifierType == "column_major") + { + qualifier.matrixPacking = EmpColumnMajor; + } + else if(qualifierType == "location") { error(qualifierTypeLine, "invalid layout qualifier", qualifierType.c_str(), "location requires an argument"); recover(); @@ -1458,6 +1859,8 @@ TLayoutQualifier qualifier; qualifier.location = -1; + qualifier.matrixPacking = EmpUnspecified; + qualifier.blockStorage = EbsUnspecified; if (qualifierType != "location") { @@ -1489,6 +1892,14 @@ { joinedQualifier.location = rightQualifier.location; } + if(rightQualifier.matrixPacking != EmpUnspecified) + { + joinedQualifier.matrixPacking = rightQualifier.matrixPacking; + } + if(rightQualifier.blockStorage != EbsUnspecified) + { + joinedQualifier.blockStorage = rightQualifier.blockStorage; + } return joinedQualifier; } @@ -1539,6 +1950,98 @@ return type; } +TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecifier, TFieldList *fieldList) +{ + if(voidErrorCheck(typeSpecifier.line, (*fieldList)[0]->name(), typeSpecifier)) + { + recover(); + } + + for(unsigned int i = 0; i < fieldList->size(); ++i) + { + // + // Careful not to replace already known aspects of type, like array-ness + // + TType *type = (*fieldList)[i]->type(); + type->setBasicType(typeSpecifier.type); + type->setNominalSize(typeSpecifier.primarySize); + type->setSecondarySize(typeSpecifier.secondarySize); + type->setPrecision(typeSpecifier.precision); + type->setQualifier(typeSpecifier.qualifier); + type->setLayoutQualifier(typeSpecifier.layoutQualifier); + + // don't allow arrays of arrays + if(type->isArray()) + { + if(arrayTypeErrorCheck(typeSpecifier.line, typeSpecifier)) + recover(); + } + if(typeSpecifier.array) + type->setArraySize(typeSpecifier.arraySize); + if(typeSpecifier.userDef) + { + type->setStruct(typeSpecifier.userDef->getStruct()); + } + + if(structNestingErrorCheck(typeSpecifier.line, *(*fieldList)[i])) + { + recover(); + } + } + + return fieldList; +} + +TPublicType TParseContext::addStructure(const TSourceLoc &structLine, const TSourceLoc &nameLine, + const TString *structName, TFieldList *fieldList) +{ + TStructure *structure = new TStructure(structName, fieldList); + TType *structureType = new TType(structure); + + // Store a bool in the struct if we're at global scope, to allow us to + // skip the local struct scoping workaround in HLSL. + structure->setUniqueId(TSymbolTableLevel::nextUniqueId()); + structure->setAtGlobalScope(symbolTable.atGlobalLevel()); + + if(!structName->empty()) + { + if(reservedErrorCheck(nameLine, *structName)) + { + recover(); + } + TVariable *userTypeDef = new TVariable(structName, *structureType, true); + if(!symbolTable.declare(*userTypeDef)) + { + error(nameLine, "redefinition", structName->c_str(), "struct"); + recover(); + } + } + + // ensure we do not specify any storage qualifiers on the struct members + for(unsigned int typeListIndex = 0; typeListIndex < fieldList->size(); typeListIndex++) + { + const TField &field = *(*fieldList)[typeListIndex]; + const TQualifier qualifier = field.type()->getQualifier(); + switch(qualifier) + { + case EvqGlobal: + case EvqTemporary: + break; + default: + error(field.line(), "invalid qualifier on struct member", getQualifierString(qualifier)); + recover(); + break; + } + } + + TPublicType publicType; + publicType.setBasic(EbtStruct, EvqTemporary, structLine); + publicType.userDef = structureType; + exitStructDeclaration(); + + return publicType; +} + bool TParseContext::enterStructDeclaration(int line, const TString& identifier) { ++structNestingLevel; @@ -1559,6 +2062,210 @@ --structNestingLevel; } +bool TParseContext::structNestingErrorCheck(const TSourceLoc &line, const TField &field) +{ + static const int kWebGLMaxStructNesting = 4; + + if(field.type()->getBasicType() != EbtStruct) + { + return false; + } + + // We're already inside a structure definition at this point, so add + // one to the field's struct nesting. + if(1 + field.type()->getDeepestStructNesting() > kWebGLMaxStructNesting) + { + std::stringstream reasonStream; + reasonStream << "Reference of struct type " + << field.type()->getStruct()->name().c_str() + << " exceeds maximum allowed nesting level of " + << kWebGLMaxStructNesting; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), field.name().c_str(), ""); + return true; + } + + return false; +} + +TIntermTyped *TParseContext::createUnaryMath(TOperator op, TIntermTyped *child, const TSourceLoc &loc, const TType *funcReturnType) +{ + if(child == nullptr) + { + return nullptr; + } + + switch(op) + { + case EOpLogicalNot: + if(child->getBasicType() != EbtBool || + child->isMatrix() || + child->isArray() || + child->isVector()) + { + return nullptr; + } + break; + case EOpBitwiseNot: + if((child->getBasicType() != EbtInt && child->getBasicType() != EbtUInt) || + child->isMatrix() || + child->isArray()) + { + return nullptr; + } + break; + case EOpPostIncrement: + case EOpPreIncrement: + case EOpPostDecrement: + case EOpPreDecrement: + case EOpNegative: + if(child->getBasicType() == EbtStruct || + child->getBasicType() == EbtBool || + child->isArray()) + { + return nullptr; + } + // Operators for built-ins are already type checked against their prototype. + default: + break; + } + + return intermediate.addUnaryMath(op, child, loc); // FIXME , funcReturnType); +} + +TIntermTyped *TParseContext::addUnaryMath(TOperator op, TIntermTyped *child, const TSourceLoc &loc) +{ + TIntermTyped *node = createUnaryMath(op, child, loc, nullptr); + if(node == nullptr) + { + unaryOpError(loc, getOperatorString(op), child->getCompleteString()); + recover(); + return child; + } + return node; +} + +TIntermTyped *TParseContext::addUnaryMathLValue(TOperator op, TIntermTyped *child, const TSourceLoc &loc) +{ + if(lValueErrorCheck(loc, getOperatorString(op), child)) + recover(); + return addUnaryMath(op, child, loc); +} + +bool TParseContext::binaryOpCommonCheck(TOperator op, TIntermTyped *left, TIntermTyped *right, const TSourceLoc &loc) +{ + if(left->isArray() || right->isArray()) + { + if(shaderVersion < 300) + { + error(loc, "Invalid operation for arrays", getOperatorString(op)); + return false; + } + + if(left->isArray() != right->isArray()) + { + error(loc, "array / non-array mismatch", getOperatorString(op)); + return false; + } + + switch(op) + { + case EOpEqual: + case EOpNotEqual: + case EOpAssign: + case EOpInitialize: + break; + default: + error(loc, "Invalid operation for arrays", getOperatorString(op)); + return false; + } + // At this point, size of implicitly sized arrays should be resolved. + if(left->getArraySize() != right->getArraySize()) + { + error(loc, "array size mismatch", getOperatorString(op)); + return false; + } + } + + // Check ops which require integer / ivec parameters + bool isBitShift = false; + switch(op) + { + case EOpBitShiftLeft: + case EOpBitShiftRight: + case EOpBitShiftLeftAssign: + case EOpBitShiftRightAssign: + // Unsigned can be bit-shifted by signed and vice versa, but we need to + // check that the basic type is an integer type. + isBitShift = true; + if(!IsInteger(left->getBasicType()) || !IsInteger(right->getBasicType())) + { + return false; + } + break; + case EOpBitwiseAnd: + case EOpBitwiseXor: + case EOpBitwiseOr: + case EOpBitwiseAndAssign: + case EOpBitwiseXorAssign: + case EOpBitwiseOrAssign: + // It is enough to check the type of only one operand, since later it + // is checked that the operand types match. + if(!IsInteger(left->getBasicType())) + { + return false; + } + break; + default: + break; + } + + // GLSL ES 1.00 and 3.00 do not support implicit type casting. + // So the basic type should usually match. + if(!isBitShift && left->getBasicType() != right->getBasicType()) + { + return false; + } + + // Check that type sizes match exactly on ops that require that. + // Also check restrictions for structs that contain arrays or samplers. + switch(op) + { + case EOpAssign: + case EOpInitialize: + case EOpEqual: + case EOpNotEqual: + // ESSL 1.00 sections 5.7, 5.8, 5.9 + if(shaderVersion < 300 && left->getType().isStructureContainingArrays()) + { + error(loc, "undefined operation for structs containing arrays", getOperatorString(op)); + return false; + } + // Samplers as l-values are disallowed also in ESSL 3.00, see section 4.1.7, + // we interpret the spec so that this extends to structs containing samplers, + // similarly to ESSL 1.00 spec. + if((shaderVersion < 300 || op == EOpAssign || op == EOpInitialize) && + left->getType().isStructureContainingSamplers()) + { + error(loc, "undefined operation for structs containing samplers", getOperatorString(op)); + return false; + } + case EOpLessThan: + case EOpGreaterThan: + case EOpLessThanEqual: + case EOpGreaterThanEqual: + if((left->getNominalSize() != right->getNominalSize()) || + (left->getSecondarySize() != right->getSecondarySize())) + { + return false; + } + default: + break; + } + + return true; +} + // // Parse an array of strings using yyparse. //