diff options
Diffstat (limited to 'src/3rdparty/angle/src/compiler/translator/ParseContext.cpp')
-rw-r--r-- | src/3rdparty/angle/src/compiler/translator/ParseContext.cpp | 6080 |
1 files changed, 3979 insertions, 2101 deletions
diff --git a/src/3rdparty/angle/src/compiler/translator/ParseContext.cpp b/src/3rdparty/angle/src/compiler/translator/ParseContext.cpp index 235351cf41..c97f91d781 100644 --- a/src/3rdparty/angle/src/compiler/translator/ParseContext.cpp +++ b/src/3rdparty/angle/src/compiler/translator/ParseContext.cpp @@ -9,34 +9,252 @@ #include <stdarg.h> #include <stdio.h> +#include "common/mathutil.h" #include "compiler/preprocessor/SourceLocation.h" #include "compiler/translator/Cache.h" -#include "compiler/translator/glslang.h" -#include "compiler/translator/ValidateSwitch.h" +#include "compiler/translator/IntermNode_util.h" #include "compiler/translator/ValidateGlobalInitializer.h" +#include "compiler/translator/ValidateSwitch.h" +#include "compiler/translator/glslang.h" #include "compiler/translator/util.h" +namespace sh +{ + /////////////////////////////////////////////////////////////////////// // // Sub- vector and matrix fields // //////////////////////////////////////////////////////////////////////// -// -// Look at a '.' field selector string and change it into offsets -// for a vector. -// -bool TParseContext::parseVectorFields(const TString &compString, +namespace +{ + +const int kWebGLMaxStructNesting = 4; + +const std::array<const char *, 8> kAtomicBuiltin = {{"atomicAdd", "atomicMin", "atomicMax", + "atomicAnd", "atomicOr", "atomicXor", + "atomicExchange", "atomicCompSwap"}}; + +bool IsAtomicBuiltin(const TString &name) +{ + for (size_t i = 0; i < kAtomicBuiltin.size(); ++i) + { + if (name.compare(kAtomicBuiltin[i]) == 0) + { + return true; + } + } + return false; +} + +bool ContainsSampler(const TStructure *structType); + +bool ContainsSampler(const TType &type) +{ + if (IsSampler(type.getBasicType())) + { + return true; + } + if (type.getBasicType() == EbtStruct) + { + return ContainsSampler(type.getStruct()); + } + + return false; +} + +bool ContainsSampler(const TStructure *structType) +{ + for (const auto &field : structType->fields()) + { + if (ContainsSampler(*field->type())) + return true; + } + return false; +} + +// Get a token from an image argument to use as an error message token. +const char *GetImageArgumentToken(TIntermTyped *imageNode) +{ + ASSERT(IsImage(imageNode->getBasicType())); + while (imageNode->getAsBinaryNode() && + (imageNode->getAsBinaryNode()->getOp() == EOpIndexIndirect || + imageNode->getAsBinaryNode()->getOp() == EOpIndexDirect)) + { + imageNode = imageNode->getAsBinaryNode()->getLeft(); + } + TIntermSymbol *imageSymbol = imageNode->getAsSymbolNode(); + if (imageSymbol) + { + return imageSymbol->getSymbol().c_str(); + } + return "image"; +} + +bool CanSetDefaultPrecisionOnType(const TPublicType &type) +{ + if (!SupportsPrecision(type.getBasicType())) + { + return false; + } + if (type.getBasicType() == EbtUInt) + { + // ESSL 3.00.4 section 4.5.4 + return false; + } + if (type.isAggregate()) + { + // Not allowed to set for aggregate types + return false; + } + return true; +} + +// Map input primitive types to input array sizes in a geometry shader. +GLuint GetGeometryShaderInputArraySize(TLayoutPrimitiveType primitiveType) +{ + switch (primitiveType) + { + case EptPoints: + return 1u; + case EptLines: + return 2u; + case EptTriangles: + return 3u; + case EptLinesAdjacency: + return 4u; + case EptTrianglesAdjacency: + return 6u; + default: + UNREACHABLE(); + return 0u; + } +} + +bool IsBufferOrSharedVariable(TIntermTyped *var) +{ + if (var->isInterfaceBlock() || var->getQualifier() == EvqBuffer || + var->getQualifier() == EvqShared) + { + return true; + } + return false; +} + +} // namespace + +// This tracks each binding point's current default offset for inheritance of subsequent +// variables using the same binding, and keeps offsets unique and non overlapping. +// See GLSL ES 3.1, section 4.4.6. +class TParseContext::AtomicCounterBindingState +{ + public: + AtomicCounterBindingState() : mDefaultOffset(0) {} + // Inserts a new span and returns -1 if overlapping, else returns the starting offset of + // newly inserted span. + int insertSpan(int start, size_t length) + { + gl::RangeI newSpan(start, start + static_cast<int>(length)); + for (const auto &span : mSpans) + { + if (newSpan.intersects(span)) + { + return -1; + } + } + mSpans.push_back(newSpan); + mDefaultOffset = newSpan.high(); + return start; + } + // Inserts a new span starting from the default offset. + int appendSpan(size_t length) { return insertSpan(mDefaultOffset, length); } + void setDefaultOffset(int offset) { mDefaultOffset = offset; } + + private: + int mDefaultOffset; + std::vector<gl::RangeI> mSpans; +}; + +TParseContext::TParseContext(TSymbolTable &symt, + TExtensionBehavior &ext, + sh::GLenum type, + ShShaderSpec spec, + ShCompileOptions options, + bool checksPrecErrors, + TDiagnostics *diagnostics, + const ShBuiltInResources &resources) + : symbolTable(symt), + mDeferredNonEmptyDeclarationErrorCheck(false), + mShaderType(type), + mShaderSpec(spec), + mCompileOptions(options), + mShaderVersion(100), + mTreeRoot(nullptr), + mLoopNestingLevel(0), + mStructNestingLevel(0), + mSwitchNestingLevel(0), + mCurrentFunctionType(nullptr), + mFunctionReturnsValue(false), + mChecksPrecisionErrors(checksPrecErrors), + mFragmentPrecisionHighOnESSL1(false), + mDefaultUniformMatrixPacking(EmpColumnMajor), + mDefaultUniformBlockStorage(sh::IsWebGLBasedSpec(spec) ? EbsStd140 : EbsShared), + mDefaultBufferMatrixPacking(EmpColumnMajor), + mDefaultBufferBlockStorage(sh::IsWebGLBasedSpec(spec) ? EbsStd140 : EbsShared), + mDiagnostics(diagnostics), + mDirectiveHandler(ext, + *mDiagnostics, + mShaderVersion, + mShaderType, + resources.WEBGL_debug_shader_precision == 1), + mPreprocessor(mDiagnostics, &mDirectiveHandler, pp::PreprocessorSettings()), + mScanner(nullptr), + mUsesFragData(false), + mUsesFragColor(false), + mUsesSecondaryOutputs(false), + mMinProgramTexelOffset(resources.MinProgramTexelOffset), + mMaxProgramTexelOffset(resources.MaxProgramTexelOffset), + mMinProgramTextureGatherOffset(resources.MinProgramTextureGatherOffset), + mMaxProgramTextureGatherOffset(resources.MaxProgramTextureGatherOffset), + mComputeShaderLocalSizeDeclared(false), + mComputeShaderLocalSize(-1), + mNumViews(-1), + mMaxNumViews(resources.MaxViewsOVR), + mMaxImageUnits(resources.MaxImageUnits), + mMaxCombinedTextureImageUnits(resources.MaxCombinedTextureImageUnits), + mMaxUniformLocations(resources.MaxUniformLocations), + mMaxUniformBufferBindings(resources.MaxUniformBufferBindings), + mMaxAtomicCounterBindings(resources.MaxAtomicCounterBindings), + mMaxShaderStorageBufferBindings(resources.MaxShaderStorageBufferBindings), + mDeclaringFunction(false), + mGeometryShaderInputPrimitiveType(EptUndefined), + mGeometryShaderOutputPrimitiveType(EptUndefined), + mGeometryShaderInvocations(0), + mGeometryShaderMaxVertices(-1), + mMaxGeometryShaderInvocations(resources.MaxGeometryShaderInvocations), + mMaxGeometryShaderMaxVertices(resources.MaxGeometryOutputVertices), + mGeometryShaderInputArraySize(0u) +{ +} + +TParseContext::~TParseContext() +{ +} + +bool TParseContext::parseVectorFields(const TSourceLoc &line, + const TString &compString, int vecSize, - TVectorFields &fields, - const TSourceLoc &line) + TVector<int> *fieldOffsets) { - fields.num = (int)compString.size(); - if (fields.num > 4) + ASSERT(fieldOffsets); + size_t fieldCount = compString.size(); + if (fieldCount > 4u) { error(line, "illegal vector field selection", compString.c_str()); return false; } + fieldOffsets->resize(fieldCount); enum { @@ -45,57 +263,57 @@ bool TParseContext::parseVectorFields(const TString &compString, estpq } fieldSet[4]; - for (int i = 0; i < fields.num; ++i) + for (unsigned int i = 0u; i < fieldOffsets->size(); ++i) { switch (compString[i]) { case 'x': - fields.offsets[i] = 0; + (*fieldOffsets)[i] = 0; fieldSet[i] = exyzw; break; case 'r': - fields.offsets[i] = 0; + (*fieldOffsets)[i] = 0; fieldSet[i] = ergba; break; case 's': - fields.offsets[i] = 0; + (*fieldOffsets)[i] = 0; fieldSet[i] = estpq; break; case 'y': - fields.offsets[i] = 1; + (*fieldOffsets)[i] = 1; fieldSet[i] = exyzw; break; case 'g': - fields.offsets[i] = 1; + (*fieldOffsets)[i] = 1; fieldSet[i] = ergba; break; case 't': - fields.offsets[i] = 1; + (*fieldOffsets)[i] = 1; fieldSet[i] = estpq; break; case 'z': - fields.offsets[i] = 2; + (*fieldOffsets)[i] = 2; fieldSet[i] = exyzw; break; case 'b': - fields.offsets[i] = 2; + (*fieldOffsets)[i] = 2; fieldSet[i] = ergba; break; case 'p': - fields.offsets[i] = 2; + (*fieldOffsets)[i] = 2; fieldSet[i] = estpq; break; case 'w': - fields.offsets[i] = 3; + (*fieldOffsets)[i] = 3; fieldSet[i] = exyzw; break; case 'a': - fields.offsets[i] = 3; + (*fieldOffsets)[i] = 3; fieldSet[i] = ergba; break; case 'q': - fields.offsets[i] = 3; + (*fieldOffsets)[i] = 3; fieldSet[i] = estpq; break; default: @@ -104,9 +322,9 @@ bool TParseContext::parseVectorFields(const TString &compString, } } - for (int i = 0; i < fields.num; ++i) + for (unsigned int i = 0u; i < fieldOffsets->size(); ++i) { - if (fields.offsets[i] >= vecSize) + if ((*fieldOffsets)[i] >= vecSize) { error(line, "vector field selection out of range", compString.c_str()); return false; @@ -133,51 +351,30 @@ bool TParseContext::parseVectorFields(const TString &compString, //////////////////////////////////////////////////////////////////////// // -// Track whether errors have occurred. -// -void TParseContext::recover() -{ -} - -// // Used by flex/bison to output all syntax and parsing errors. // -void TParseContext::error(const TSourceLoc &loc, - const char *reason, - const char *token, - const char *extraInfo) +void TParseContext::error(const TSourceLoc &loc, const char *reason, const char *token) { - pp::SourceLocation srcLoc; - srcLoc.file = loc.first_file; - srcLoc.line = loc.first_line; - mDiagnostics.writeInfo(pp::Diagnostics::PP_ERROR, srcLoc, reason, token, extraInfo); + mDiagnostics->error(loc, reason, token); } -void TParseContext::warning(const TSourceLoc &loc, - const char *reason, - const char *token, - const char *extraInfo) +void TParseContext::warning(const TSourceLoc &loc, const char *reason, const char *token) { - pp::SourceLocation srcLoc; - srcLoc.file = loc.first_file; - srcLoc.line = loc.first_line; - mDiagnostics.writeInfo(pp::Diagnostics::PP_WARNING, srcLoc, reason, token, extraInfo); + mDiagnostics->warning(loc, reason, token); } void TParseContext::outOfRangeError(bool isError, const TSourceLoc &loc, const char *reason, - const char *token, - const char *extraInfo) + const char *token) { if (isError) { - error(loc, reason, token, extraInfo); - recover(); + error(loc, reason, token); } else { - warning(loc, reason, token, extraInfo); + warning(loc, reason, token); } } @@ -186,10 +383,10 @@ void TParseContext::outOfRangeError(bool isError, // void TParseContext::assignError(const TSourceLoc &line, const char *op, TString left, TString right) { - std::stringstream extraInfoStream; - extraInfoStream << "cannot convert from '" << right << "' to '" << left << "'"; - std::string extraInfo = extraInfoStream.str(); - error(line, "", op, extraInfo.c_str()); + std::stringstream reasonStream; + reasonStream << "cannot convert from '" << right << "' to '" << left << "'"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), op); } // @@ -197,11 +394,12 @@ void TParseContext::assignError(const TSourceLoc &line, const char *op, TString // void TParseContext::unaryOpError(const TSourceLoc &line, const char *op, TString operand) { - std::stringstream extraInfoStream; - extraInfoStream << "no operation '" << op << "' exists that takes an operand of type " - << operand << " (or there is no acceptable conversion)"; - std::string extraInfo = extraInfoStream.str(); - error(line, " wrong operand type", op, extraInfo.c_str()); + std::stringstream reasonStream; + reasonStream << "wrong operand type - no operation '" << op + << "' exists that takes an operand of type " << operand + << " (or there is no acceptable conversion)"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), op); } // @@ -212,102 +410,85 @@ void TParseContext::binaryOpError(const TSourceLoc &line, TString left, TString right) { - std::stringstream extraInfoStream; - extraInfoStream << "no operation '" << op << "' exists that takes a left-hand operand of type '" - << left << "' and a right operand of type '" << right - << "' (or there is no acceptable conversion)"; - std::string extraInfo = extraInfoStream.str(); - error(line, " wrong operand types ", op, extraInfo.c_str()); + std::stringstream reasonStream; + reasonStream << "wrong operand types - no operation '" << op + << "' exists that takes a left-hand operand of type '" << left + << "' and a right operand of type '" << right + << "' (or there is no acceptable conversion)"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), op); } -bool TParseContext::precisionErrorCheck(const TSourceLoc &line, - TPrecision precision, - TBasicType type) +void TParseContext::checkPrecisionSpecified(const TSourceLoc &line, + TPrecision precision, + TBasicType type) { if (!mChecksPrecisionErrors) - return false; + return; + + if (precision != EbpUndefined && !SupportsPrecision(type)) + { + error(line, "illegal type for precision qualifier", getBasicString(type)); + } + if (precision == EbpUndefined) { switch (type) { case EbtFloat: error(line, "No precision specified for (float)", ""); - return true; + return; case EbtInt: case EbtUInt: UNREACHABLE(); // there's always a predeclared qualifier error(line, "No precision specified (int)", ""); - return true; + return; default: - if (IsSampler(type)) + if (IsOpaqueType(type)) { - error(line, "No precision specified (sampler)", ""); - return true; + error(line, "No precision specified", getBasicString(type)); + return; } } } - return false; } -// // Both test and if necessary, spit out an error, to see if the node is really // an l-value that can be operated on this way. -// -// Returns true if the was an error. -// -bool TParseContext::lValueErrorCheck(const TSourceLoc &line, const char *op, TIntermTyped *node) +bool TParseContext::checkCanBeLValue(const TSourceLoc &line, const char *op, TIntermTyped *node) { - TIntermSymbol *symNode = node->getAsSymbolNode(); - TIntermBinary *binaryNode = node->getAsBinaryNode(); + TIntermSymbol *symNode = node->getAsSymbolNode(); + TIntermBinary *binaryNode = node->getAsBinaryNode(); + TIntermSwizzle *swizzleNode = node->getAsSwizzleNode(); - if (binaryNode) + if (swizzleNode) { - bool errorReturn; + bool ok = checkCanBeLValue(line, op, swizzleNode->getOperand()); + if (ok && swizzleNode->hasDuplicateOffsets()) + { + error(line, " l-value of swizzle cannot have duplicate components", op); + return false; + } + return ok; + } + if (binaryNode) + { switch (binaryNode->getOp()) { case EOpIndexDirect: case EOpIndexIndirect: case EOpIndexDirectStruct: case EOpIndexDirectInterfaceBlock: - return lValueErrorCheck(line, op, binaryNode->getLeft()); - case EOpVectorSwizzle: - errorReturn = lValueErrorCheck(line, op, binaryNode->getLeft()); - if (!errorReturn) - { - int offset[4] = {0, 0, 0, 0}; - - TIntermTyped *rightNode = binaryNode->getRight(); - TIntermAggregate *aggrNode = rightNode->getAsAggregate(); - - for (TIntermSequence::iterator p = aggrNode->getSequence()->begin(); - p != aggrNode->getSequence()->end(); p++) - { - int value = (*p)->getAsTyped()->getAsConstantUnion()->getIConst(0); - offset[value]++; - if (offset[value] > 1) - { - error(line, " l-value of swizzle cannot have duplicate components", op); - - return true; - } - } - } - - return errorReturn; + return checkCanBeLValue(line, op, binaryNode->getLeft()); default: break; } error(line, " l-value required", op); - - return true; + return false; } - const char *symbol = 0; - if (symNode != 0) - symbol = symNode->getSymbol().c_str(); - - const char *message = 0; + std::string message; switch (node->getQualifier()) { case EvqConst: @@ -320,9 +501,11 @@ bool TParseContext::lValueErrorCheck(const TSourceLoc &line, const char *op, TIn message = "can't modify an attribute"; break; case EvqFragmentIn: - message = "can't modify an input"; - break; case EvqVertexIn: + case EvqGeometryIn: + case EvqFlatIn: + case EvqSmoothIn: + case EvqCentroidIn: message = "can't modify an input"; break; case EvqUniform: @@ -340,6 +523,51 @@ bool TParseContext::lValueErrorCheck(const TSourceLoc &line, const char *op, TIn case EvqPointCoord: message = "can't modify gl_PointCoord"; break; + case EvqNumWorkGroups: + message = "can't modify gl_NumWorkGroups"; + break; + case EvqWorkGroupSize: + message = "can't modify gl_WorkGroupSize"; + break; + case EvqWorkGroupID: + message = "can't modify gl_WorkGroupID"; + break; + case EvqLocalInvocationID: + message = "can't modify gl_LocalInvocationID"; + break; + case EvqGlobalInvocationID: + message = "can't modify gl_GlobalInvocationID"; + break; + case EvqLocalInvocationIndex: + message = "can't modify gl_LocalInvocationIndex"; + break; + case EvqViewIDOVR: + message = "can't modify gl_ViewID_OVR"; + break; + case EvqComputeIn: + message = "can't modify work group size variable"; + break; + case EvqPerVertexIn: + message = "can't modify any member in gl_in"; + break; + case EvqPrimitiveIDIn: + message = "can't modify gl_PrimitiveIDIn"; + break; + case EvqInvocationID: + message = "can't modify gl_InvocationID"; + break; + case EvqPrimitiveID: + if (mShaderType == GL_FRAGMENT_SHADER) + { + message = "can't modify gl_PrimitiveID in a fragment shader"; + } + break; + case EvqLayer: + if (mShaderType == GL_FRAGMENT_SHADER) + { + message = "can't modify gl_Layer in a fragment shader"; + } + break; default: // // Type that can't be written to? @@ -348,287 +576,270 @@ bool TParseContext::lValueErrorCheck(const TSourceLoc &line, const char *op, TIn { message = "can't modify void"; } - if (IsSampler(node->getBasicType())) + if (IsOpaqueType(node->getBasicType())) { - message = "can't modify a sampler"; + message = "can't modify a variable with type "; + message += getBasicString(node->getBasicType()); + } + else if (node->getMemoryQualifier().readonly) + { + message = "can't modify a readonly variable"; } } - if (message == 0 && binaryNode == 0 && symNode == 0) + if (message.empty() && binaryNode == 0 && symNode == 0) { - error(line, " l-value required", op); + error(line, "l-value required", op); - return true; + return false; } // // Everything else is okay, no error. // - if (message == 0) - return false; + if (message.empty()) + return true; // // If we get here, we have an error and a message. // if (symNode) { - std::stringstream extraInfoStream; - extraInfoStream << "\"" << symbol << "\" (" << message << ")"; - std::string extraInfo = extraInfoStream.str(); - error(line, " l-value required", op, extraInfo.c_str()); + const char *symbol = symNode->getSymbol().c_str(); + std::stringstream reasonStream; + reasonStream << "l-value required (" << message << " \"" << symbol << "\")"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), op); } else { - std::stringstream extraInfoStream; - extraInfoStream << "(" << message << ")"; - std::string extraInfo = extraInfoStream.str(); - error(line, " l-value required", op, extraInfo.c_str()); + std::stringstream reasonStream; + reasonStream << "l-value required (" << message << ")"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), op); } - return true; + return false; } -// // Both test, and if necessary spit out an error, to see if the node is really // a constant. -// -// Returns true if the was an error. -// -bool TParseContext::constErrorCheck(TIntermTyped *node) +void TParseContext::checkIsConst(TIntermTyped *node) { - if (node->getQualifier() == EvqConst) - return false; - - error(node->getLine(), "constant expression required", ""); - - return true; + if (node->getQualifier() != EvqConst) + { + error(node->getLine(), "constant expression required", ""); + } } -// // Both test, and if necessary spit out an error, to see if the node is really // an integer. -// -// Returns true if the was an error. -// -bool TParseContext::integerErrorCheck(TIntermTyped *node, const char *token) +void TParseContext::checkIsScalarInteger(TIntermTyped *node, const char *token) { - if (node->isScalarInt()) - return false; - - error(node->getLine(), "integer expression required", token); - - return true; + if (!node->isScalarInt()) + { + error(node->getLine(), "integer expression required", token); + } } -// // Both test, and if necessary spit out an error, to see if we are currently // globally scoped. -// -// Returns true if the was an error. -// -bool TParseContext::globalErrorCheck(const TSourceLoc &line, bool global, const char *token) +bool TParseContext::checkIsAtGlobalLevel(const TSourceLoc &line, const char *token) { - if (global) + if (!symbolTable.atGlobalLevel()) + { + error(line, "only allowed at global scope", token); return false; - - error(line, "only allowed at global scope", token); - + } return true; } -// -// For now, keep it simple: if it starts "gl_", it's reserved, independent -// of scope. Except, if the symbol table is at the built-in push-level, -// which is when we are parsing built-ins. -// Also checks for "webgl_" and "_webgl_" reserved identifiers if parsing a -// webgl shader. -// -// Returns true if there was an error. -// -bool TParseContext::reservedErrorCheck(const TSourceLoc &line, const TString &identifier) +// ESSL 3.00.5 sections 3.8 and 3.9. +// If it starts "gl_" or contains two consecutive underscores, it's reserved. +// Also checks for "webgl_" and "_webgl_" reserved identifiers if parsing a webgl shader. +bool TParseContext::checkIsNotReserved(const TSourceLoc &line, const TString &identifier) { static const char *reservedErrMsg = "reserved built-in name"; - if (!symbolTable.atBuiltInLevel()) + if (identifier.compare(0, 3, "gl_") == 0) { - if (identifier.compare(0, 3, "gl_") == 0) - { - error(line, reservedErrMsg, "gl_"); - return true; - } - if (IsWebGLBasedSpec(mShaderSpec)) + error(line, reservedErrMsg, "gl_"); + return false; + } + if (sh::IsWebGLBasedSpec(mShaderSpec)) + { + if (identifier.compare(0, 6, "webgl_") == 0) { - if (identifier.compare(0, 6, "webgl_") == 0) - { - error(line, reservedErrMsg, "webgl_"); - return true; - } - if (identifier.compare(0, 7, "_webgl_") == 0) - { - error(line, reservedErrMsg, "_webgl_"); - return true; - } - if (mShaderSpec == SH_CSS_SHADERS_SPEC && identifier.compare(0, 4, "css_") == 0) - { - error(line, reservedErrMsg, "css_"); - return true; - } + error(line, reservedErrMsg, "webgl_"); + return false; } - if (identifier.find("__") != TString::npos) + if (identifier.compare(0, 7, "_webgl_") == 0) { - error(line, - "identifiers containing two consecutive underscores (__) are reserved as " - "possible future keywords", - identifier.c_str()); - return true; + error(line, reservedErrMsg, "_webgl_"); + return false; } } - - return false; -} - -// -// Make sure there is enough data provided to the constructor to build -// something of the type of the constructor. Also returns the type of -// the constructor. -// -// Returns true if there was an error in construction. -// -bool TParseContext::constructorErrorCheck(const TSourceLoc &line, - TIntermNode *argumentsNode, - TFunction &function, - TOperator op, - TType *type) -{ - *type = function.getReturnType(); - - bool constructingMatrix = false; - switch (op) + if (identifier.find("__") != TString::npos) { - case EOpConstructMat2: - case EOpConstructMat2x3: - case EOpConstructMat2x4: - case EOpConstructMat3x2: - case EOpConstructMat3: - case EOpConstructMat3x4: - case EOpConstructMat4x2: - case EOpConstructMat4x3: - case EOpConstructMat4: - constructingMatrix = true; - break; - default: - break; + error(line, + "identifiers containing two consecutive underscores (__) are reserved as " + "possible future keywords", + identifier.c_str()); + return false; } + return true; +} - // - // Note: It's okay to have too many components available, but not okay to have unused - // arguments. 'full' will go to true when enough args have been seen. If we loop - // again, there is an extra argument, so 'overfull' will become true. - // - - size_t size = 0; - bool constType = true; - bool full = false; - bool overFull = false; - bool matrixInMatrix = false; - bool arrayArg = false; - for (size_t i = 0; i < function.getParamCount(); ++i) +// Make sure the argument types are correct for constructing a specific type. +bool TParseContext::checkConstructorArguments(const TSourceLoc &line, + const TIntermSequence *arguments, + const TType &type) +{ + if (arguments->empty()) { - const TConstParameter ¶m = function.getParam(i); - size += param.type->getObjectSize(); - - if (constructingMatrix && param.type->isMatrix()) - matrixInMatrix = true; - if (full) - overFull = true; - if (op != EOpConstructStruct && !type->isArray() && size >= type->getObjectSize()) - full = true; - if (param.type->getQualifier() != EvqConst) - constType = false; - if (param.type->isArray()) - arrayArg = true; + error(line, "constructor does not have any arguments", "constructor"); + return false; } - if (constType) - type->setQualifier(EvqConst); - - if (type->isArray()) + for (TIntermNode *arg : *arguments) { - if (type->isUnsizedArray()) + const TIntermTyped *argTyped = arg->getAsTyped(); + ASSERT(argTyped != nullptr); + if (type.getBasicType() != EbtStruct && IsOpaqueType(argTyped->getBasicType())) { - type->setArraySize(static_cast<int>(function.getParamCount())); + std::string reason("cannot convert a variable with type "); + reason += getBasicString(argTyped->getBasicType()); + error(line, reason.c_str(), "constructor"); + return false; } - else if (static_cast<size_t>(type->getArraySize()) != function.getParamCount()) + else if (argTyped->getMemoryQualifier().writeonly) { - error(line, "array constructor needs one argument per array element", "constructor"); - return true; + error(line, "cannot convert a variable with writeonly", "constructor"); + return false; + } + if (argTyped->getBasicType() == EbtVoid) + { + error(line, "cannot convert a void", "constructor"); + return false; } } - if (arrayArg && op != EOpConstructStruct) + if (type.isArray()) { - error(line, "constructing from a non-dereferenced array", "constructor"); - return true; + // The size of an unsized constructor should already have been determined. + ASSERT(!type.isUnsizedArray()); + if (static_cast<size_t>(type.getOutermostArraySize()) != arguments->size()) + { + error(line, "array constructor needs one argument per array element", "constructor"); + return false; + } + // GLSL ES 3.00 section 5.4.4: Each argument must be the same type as the element type of + // the array. + for (TIntermNode *const &argNode : *arguments) + { + const TType &argType = argNode->getAsTyped()->getType(); + if (mShaderVersion < 310 && argType.isArray()) + { + error(line, "constructing from a non-dereferenced array", "constructor"); + return false; + } + if (!argType.isElementTypeOf(type)) + { + error(line, "Array constructor argument has an incorrect type", "constructor"); + return false; + } + } } - - if (matrixInMatrix && !type->isArray()) + else if (type.getBasicType() == EbtStruct) { - if (function.getParamCount() != 1) + const TFieldList &fields = type.getStruct()->fields(); + if (fields.size() != arguments->size()) { - error(line, "constructing matrix from matrix can only take one argument", + error(line, + "Number of constructor parameters does not match the number of structure fields", "constructor"); - return true; + return false; } - } - if (overFull) - { - error(line, "too many arguments", "constructor"); - return true; + for (size_t i = 0; i < fields.size(); i++) + { + if (i >= arguments->size() || + (*arguments)[i]->getAsTyped()->getType() != *fields[i]->type()) + { + error(line, "Structure constructor arguments do not match structure fields", + "constructor"); + return false; + } + } } - - if (op == EOpConstructStruct && !type->isArray() && - type->getStruct()->fields().size() != function.getParamCount()) + else { - error(line, - "Number of constructor parameters does not match the number of structure fields", - "constructor"); - return true; - } + // We're constructing a scalar, vector, or matrix. - if (!type->isMatrix() || !matrixInMatrix) - { - if ((op != EOpConstructStruct && size != 1 && size < type->getObjectSize()) || - (op == EOpConstructStruct && size < type->getObjectSize())) + // Note: It's okay to have too many components available, but not okay to have unused + // arguments. 'full' will go to true when enough args have been seen. If we loop again, + // there is an extra argument, so 'overFull' will become true. + + size_t size = 0; + bool full = false; + bool overFull = false; + bool matrixArg = false; + for (TIntermNode *arg : *arguments) { - error(line, "not enough data provided for construction", "constructor"); - return true; - } - } + const TIntermTyped *argTyped = arg->getAsTyped(); + ASSERT(argTyped != nullptr); - if (argumentsNode == nullptr) - { - error(line, "constructor does not have any arguments", "constructor"); - return true; - } + if (argTyped->getBasicType() == EbtStruct) + { + error(line, "a struct cannot be used as a constructor argument for this type", + "constructor"); + return false; + } + if (argTyped->getType().isArray()) + { + error(line, "constructing from a non-dereferenced array", "constructor"); + return false; + } + if (argTyped->getType().isMatrix()) + { + matrixArg = true; + } - TIntermAggregate *argumentsAgg = argumentsNode->getAsAggregate(); - for (TIntermNode *&argNode : *argumentsAgg->getSequence()) - { - TIntermTyped *argTyped = argNode->getAsTyped(); - ASSERT(argTyped != nullptr); - if (op != EOpConstructStruct && IsSampler(argTyped->getBasicType())) + size += argTyped->getType().getObjectSize(); + if (full) + { + overFull = true; + } + if (size >= type.getObjectSize()) + { + full = true; + } + } + + if (type.isMatrix() && matrixArg) { - error(line, "cannot convert a sampler", "constructor"); - return true; + if (arguments->size() != 1) + { + error(line, "constructing matrix from matrix can only take one argument", + "constructor"); + return false; + } } - if (argTyped->getBasicType() == EbtVoid) + else { - error(line, "cannot convert a void", "constructor"); - return true; + if (size != 1 && size < type.getObjectSize()) + { + error(line, "not enough data provided for construction", "constructor"); + return false; + } + if (overFull) + { + error(line, "too many arguments", "constructor"); + return false; + } } } - return false; + return true; } // This function checks to see if a void variable has been declared and raise an error message for @@ -636,126 +847,116 @@ bool TParseContext::constructorErrorCheck(const TSourceLoc &line, // // returns true in case of an error // -bool TParseContext::voidErrorCheck(const TSourceLoc &line, +bool TParseContext::checkIsNonVoid(const TSourceLoc &line, const TString &identifier, const TBasicType &type) { if (type == EbtVoid) { error(line, "illegal use of type 'void'", identifier.c_str()); - return true; + return false; } - return false; + return true; } // This function checks to see if the node (for the expression) contains a scalar boolean expression -// or not -// -// returns true in case of an error -// -bool TParseContext::boolErrorCheck(const TSourceLoc &line, const TIntermTyped *type) +// or not. +bool TParseContext::checkIsScalarBool(const TSourceLoc &line, const TIntermTyped *type) { - if (type->getBasicType() != EbtBool || type->isArray() || type->isMatrix() || type->isVector()) + if (type->getBasicType() != EbtBool || !type->isScalar()) { error(line, "boolean expression expected", ""); - return true; + return false; } - - return false; + return true; } // This function checks to see if the node (for the expression) contains a scalar boolean expression -// or not -// -// returns true in case of an error -// -bool TParseContext::boolErrorCheck(const TSourceLoc &line, const TPublicType &pType) +// or not. +void TParseContext::checkIsScalarBool(const TSourceLoc &line, const TPublicType &pType) { - if (pType.type != EbtBool || pType.isAggregate()) + if (pType.getBasicType() != EbtBool || pType.isAggregate()) { error(line, "boolean expression expected", ""); - return true; } - - return false; } -bool TParseContext::samplerErrorCheck(const TSourceLoc &line, - const TPublicType &pType, - const char *reason) +bool TParseContext::checkIsNotOpaqueType(const TSourceLoc &line, + const TTypeSpecifierNonArray &pType, + const char *reason) { if (pType.type == EbtStruct) { - if (containsSampler(*pType.userDef)) + if (ContainsSampler(pType.userDef)) { - error(line, reason, getBasicString(pType.type), "(structure contains a sampler)"); - - return true; + std::stringstream reasonStream; + reasonStream << reason << " (structure contains a sampler)"; + std::string reasonStr = reasonStream.str(); + error(line, reasonStr.c_str(), getBasicString(pType.type)); + return false; } - - return false; + // only samplers need to be checked from structs, since other opaque types can't be struct + // members. + return true; } - else if (IsSampler(pType.type)) + else if (IsOpaqueType(pType.type)) { error(line, reason, getBasicString(pType.type)); - - return true; + return false; } - return false; + return true; } -bool TParseContext::locationDeclaratorListCheck(const TSourceLoc &line, const TPublicType &pType) +void TParseContext::checkDeclaratorLocationIsNotSpecified(const TSourceLoc &line, + const TPublicType &pType) { if (pType.layoutQualifier.location != -1) { error(line, "location must only be specified for a single input or output variable", "location"); - return true; } - - return false; } -bool TParseContext::parameterSamplerErrorCheck(const TSourceLoc &line, - TQualifier qualifier, - const TType &type) +void TParseContext::checkLocationIsNotSpecified(const TSourceLoc &location, + const TLayoutQualifier &layoutQualifier) { - if ((qualifier == EvqOut || qualifier == EvqInOut) && type.getBasicType() != EbtStruct && - IsSampler(type.getBasicType())) + if (layoutQualifier.location != -1) { - error(line, "samplers cannot be output parameters", type.getBasicString()); - return true; + const char *errorMsg = "invalid layout qualifier: only valid on program inputs and outputs"; + if (mShaderVersion >= 310) + { + errorMsg = + "invalid layout qualifier: only valid on shader inputs, outputs, and uniforms"; + } + error(location, errorMsg, "location"); } - - return false; } -bool TParseContext::containsSampler(const TType &type) +void TParseContext::checkStd430IsForShaderStorageBlock(const TSourceLoc &location, + const TLayoutBlockStorage &blockStorage, + const TQualifier &qualifier) { - if (IsSampler(type.getBasicType())) - return true; - - if (type.getBasicType() == EbtStruct || type.isInterfaceBlock()) + if (blockStorage == EbsStd430 && qualifier != EvqBuffer) { - const TFieldList &fields = type.getStruct()->fields(); - for (unsigned int i = 0; i < fields.size(); ++i) - { - if (containsSampler(*fields[i]->type())) - return true; - } + error(location, "The std430 layout is supported only for shader storage blocks.", "std430"); } +} - return false; +void TParseContext::checkOutParameterIsNotOpaqueType(const TSourceLoc &line, + TQualifier qualifier, + const TType &type) +{ + ASSERT(qualifier == EvqOut || qualifier == EvqInOut); + if (IsOpaqueType(type.getBasicType())) + { + error(line, "opaque types cannot be output parameters", type.getBasicString()); + } } -// // Do size checking for an array type's size. -// -// Returns true if there was an error. -// -bool TParseContext::arraySizeErrorCheck(const TSourceLoc &line, TIntermTyped *expr, int &size) +unsigned int TParseContext::checkIsValidArraySize(const TSourceLoc &line, TIntermTyped *expr) { TIntermConstantUnion *constant = expr->getAsConstantUnion(); @@ -765,36 +966,32 @@ bool TParseContext::arraySizeErrorCheck(const TSourceLoc &line, TIntermTyped *ex if (expr->getQualifier() != EvqConst || constant == nullptr || !constant->isScalarInt()) { error(line, "array size must be a constant integer expression", ""); - size = 1; - return true; + return 1u; } - unsigned int unsignedSize = 0; + unsigned int size = 0u; if (constant->getBasicType() == EbtUInt) { - unsignedSize = constant->getUConst(0); - size = static_cast<int>(unsignedSize); + size = constant->getUConst(0); } else { - size = constant->getIConst(0); + int signedSize = constant->getIConst(0); - if (size < 0) + if (signedSize < 0) { error(line, "array size must be non-negative", ""); - size = 1; - return true; + return 1u; } - unsignedSize = static_cast<unsigned int>(size); + size = static_cast<unsigned int>(signedSize); } - if (size == 0) + if (size == 0u) { error(line, "array size must be greater than zero", ""); - size = 1; - return true; + return 1u; } // The size of arrays is restricted here to prevent issues further down the @@ -802,76 +999,80 @@ bool TParseContext::arraySizeErrorCheck(const TSourceLoc &line, TIntermTyped *ex // 4096 registers so this should be reasonable even for aggressively optimizable code. const unsigned int sizeLimit = 65536; - if (unsignedSize > sizeLimit) + if (size > sizeLimit) { error(line, "array size too large", ""); - size = 1; - return true; + return 1u; } - return false; + return size; } -// // See if this qualifier can be an array. -// -// Returns true if there is an error. -// -bool TParseContext::arrayQualifierErrorCheck(const TSourceLoc &line, const TPublicType &type) +bool TParseContext::checkIsValidQualifierForArray(const TSourceLoc &line, + const TPublicType &elementQualifier) { - if ((type.qualifier == EvqAttribute) || (type.qualifier == EvqVertexIn) || - (type.qualifier == EvqConst && mShaderVersion < 300)) + if ((elementQualifier.qualifier == EvqAttribute) || + (elementQualifier.qualifier == EvqVertexIn) || + (elementQualifier.qualifier == EvqConst && mShaderVersion < 300)) { error(line, "cannot declare arrays of this qualifier", - TType(type).getCompleteString().c_str()); - return true; + TType(elementQualifier).getQualifierString()); + return false; } - return false; + return true; } -// -// See if this type can be an array. -// -// Returns true if there is an error. -// -bool TParseContext::arrayTypeErrorCheck(const TSourceLoc &line, const TPublicType &type) +// See if this element type can be formed into an array. +bool TParseContext::checkArrayElementIsNotArray(const TSourceLoc &line, + const TPublicType &elementType) { - // - // Can the type be an array? - // - if (type.array) + if (mShaderVersion < 310 && elementType.isArray()) { - error(line, "cannot declare arrays of arrays", TType(type).getCompleteString().c_str()); - return true; + error(line, "cannot declare arrays of arrays", + TType(elementType).getCompleteString().c_str()); + return false; + } + return true; +} + +// Check if this qualified element type can be formed into an array. This is only called when array +// brackets are associated with an identifier in a declaration, like this: +// float a[2]; +// Similar checks are done in addFullySpecifiedType for array declarations where the array brackets +// are associated with the type, like this: +// float[2] a; +bool TParseContext::checkIsValidTypeAndQualifierForArray(const TSourceLoc &indexLocation, + const TPublicType &elementType) +{ + if (!checkArrayElementIsNotArray(indexLocation, elementType)) + { + return false; } // In ESSL1.00 shaders, structs cannot be varying (section 4.3.5). This is checked elsewhere. // In ESSL3.00 shaders, struct inputs/outputs are allowed but not arrays of structs (section // 4.3.4). - if (mShaderVersion >= 300 && type.type == EbtStruct && sh::IsVarying(type.qualifier)) + if (mShaderVersion >= 300 && elementType.getBasicType() == EbtStruct && + sh::IsVarying(elementType.qualifier)) { - error(line, "cannot declare arrays of structs of this qualifier", - TType(type).getCompleteString().c_str()); - return true; + error(indexLocation, "cannot declare arrays of structs of this qualifier", + TType(elementType).getCompleteString().c_str()); + return false; } - - return false; + return checkIsValidQualifierForArray(indexLocation, elementType); } -// // Enforce non-initializer type/qualifier rules. -// -// Returns true if there was an error. -// -bool TParseContext::nonInitErrorCheck(const TSourceLoc &line, - const TString &identifier, - TPublicType *type) +void TParseContext::checkCanBeDeclaredWithoutInitializer(const TSourceLoc &line, + const TString &identifier, + TType *type) { ASSERT(type != nullptr); - if (type->qualifier == EvqConst) + if (type->getQualifier() == EvqConst) { // Make the qualifier make sense. - type->qualifier = EvqTemporary; + type->setQualifier(EvqTemporary); // Generate informative error messages for ESSL1. // In ESSL3 arrays and structures containing arrays can be constant. @@ -886,15 +1087,10 @@ bool TParseContext::nonInitErrorCheck(const TSourceLoc &line, { error(line, "variables with qualifier 'const' must be initialized", identifier.c_str()); } - - return true; } - if (type->isUnsizedArray()) - { - error(line, "implicitly sized arrays need to be initialized", identifier.c_str()); - return true; - } - return false; + // This will make the type sized if it isn't sized yet. + checkIsNotUnsizedArray(line, "implicitly sized arrays need to be initialized", + identifier.c_str(), type); } // Do some simple checks that are shared between all variable declarations, @@ -909,18 +1105,27 @@ bool TParseContext::declareVariable(const TSourceLoc &line, { ASSERT((*variable) == nullptr); - bool needsReservedErrorCheck = true; + checkBindingIsValid(line, type); + + bool needsReservedCheck = true; // gl_LastFragData may be redeclared with a new precision qualifier if (type.isArray() && identifier.compare(0, 15, "gl_LastFragData") == 0) { const TVariable *maxDrawBuffers = static_cast<const TVariable *>( symbolTable.findBuiltIn("gl_MaxDrawBuffers", mShaderVersion)); - if (type.getArraySize() == maxDrawBuffers->getConstPointer()->getIConst()) + if (type.isArrayOfArrays()) + { + error(line, "redeclaration of gl_LastFragData as an array of arrays", + identifier.c_str()); + return false; + } + else if (static_cast<int>(type.getOutermostArraySize()) == + maxDrawBuffers->getConstPointer()->getIConst()) { if (TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion)) { - needsReservedErrorCheck = extensionErrorCheck(line, builtInSymbol->getExtension()); + needsReservedCheck = !checkCanUseExtension(line, builtInSymbol->getExtension()); } } else @@ -931,77 +1136,237 @@ bool TParseContext::declareVariable(const TSourceLoc &line, } } - if (needsReservedErrorCheck && reservedErrorCheck(line, identifier)) + if (needsReservedCheck && !checkIsNotReserved(line, identifier)) return false; - (*variable) = new TVariable(&identifier, type); - if (!symbolTable.declare(*variable)) + (*variable) = symbolTable.declareVariable(&identifier, type); + if (!(*variable)) { error(line, "redefinition", identifier.c_str()); - *variable = nullptr; return false; } - if (voidErrorCheck(line, identifier, type.getBasicType())) + if (!checkIsNonVoid(line, identifier, type.getBasicType())) return false; return true; } -bool TParseContext::paramErrorCheck(const TSourceLoc &line, - TQualifier qualifier, - TQualifier paramQualifier, - TType *type) +void TParseContext::checkIsParameterQualifierValid( + const TSourceLoc &line, + const TTypeQualifierBuilder &typeQualifierBuilder, + TType *type) +{ + // The only parameter qualifiers a parameter can have are in, out, inout or const. + TTypeQualifier typeQualifier = typeQualifierBuilder.getParameterTypeQualifier(mDiagnostics); + + if (typeQualifier.qualifier == EvqOut || typeQualifier.qualifier == EvqInOut) + { + checkOutParameterIsNotOpaqueType(line, typeQualifier.qualifier, *type); + } + + if (!IsImage(type->getBasicType())) + { + checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, line); + } + else + { + type->setMemoryQualifier(typeQualifier.memoryQualifier); + } + + type->setQualifier(typeQualifier.qualifier); + + if (typeQualifier.precision != EbpUndefined) + { + type->setPrecision(typeQualifier.precision); + } +} + +template <size_t size> +bool TParseContext::checkCanUseOneOfExtensions(const TSourceLoc &line, + const std::array<TExtension, size> &extensions) { - if (qualifier != EvqConst && qualifier != EvqTemporary) + ASSERT(!extensions.empty()); + const TExtensionBehavior &extBehavior = extensionBehavior(); + + bool canUseWithWarning = false; + bool canUseWithoutWarning = false; + + const char *errorMsgString = ""; + TExtension errorMsgExtension = TExtension::UNDEFINED; + + for (TExtension extension : extensions) + { + auto extIter = extBehavior.find(extension); + if (canUseWithWarning) + { + // We already have an extension that we can use, but with a warning. + // See if we can use the alternative extension without a warning. + if (extIter == extBehavior.end()) + { + continue; + } + if (extIter->second == EBhEnable || extIter->second == EBhRequire) + { + canUseWithoutWarning = true; + break; + } + continue; + } + if (extIter == extBehavior.end()) + { + errorMsgString = "extension is not supported"; + errorMsgExtension = extension; + } + else if (extIter->second == EBhUndefined || extIter->second == EBhDisable) + { + errorMsgString = "extension is disabled"; + errorMsgExtension = extension; + } + else if (extIter->second == EBhWarn) + { + errorMsgExtension = extension; + canUseWithWarning = true; + } + else + { + ASSERT(extIter->second == EBhEnable || extIter->second == EBhRequire); + canUseWithoutWarning = true; + break; + } + } + + if (canUseWithoutWarning) { - error(line, "qualifier not allowed on function parameter", getQualifierString(qualifier)); return true; } - if (qualifier == EvqConst && paramQualifier != EvqIn) + if (canUseWithWarning) { - error(line, "qualifier not allowed with ", getQualifierString(qualifier), - getQualifierString(paramQualifier)); + warning(line, "extension is being used", GetExtensionNameString(errorMsgExtension)); return true; } + error(line, errorMsgString, GetExtensionNameString(errorMsgExtension)); + return false; +} - if (qualifier == EvqConst) - type->setQualifier(EvqConstReadOnly); +template bool TParseContext::checkCanUseOneOfExtensions( + const TSourceLoc &line, + const std::array<TExtension, 1> &extensions); +template bool TParseContext::checkCanUseOneOfExtensions( + const TSourceLoc &line, + const std::array<TExtension, 2> &extensions); +template bool TParseContext::checkCanUseOneOfExtensions( + const TSourceLoc &line, + const std::array<TExtension, 3> &extensions); + +bool TParseContext::checkCanUseExtension(const TSourceLoc &line, TExtension extension) +{ + ASSERT(extension != TExtension::UNDEFINED); + ASSERT(extension != TExtension::EXT_geometry_shader); + if (extension == TExtension::OES_geometry_shader) + { + // OES_geometry_shader and EXT_geometry_shader are always interchangeable. + constexpr std::array<TExtension, 2u> extensions{ + {TExtension::EXT_geometry_shader, TExtension::OES_geometry_shader}}; + return checkCanUseOneOfExtensions(line, extensions); + } + return checkCanUseOneOfExtensions(line, std::array<TExtension, 1u>{{extension}}); +} + +// ESSL 3.00.6 section 4.8 Empty Declarations: "The combinations of qualifiers that cause +// compile-time or link-time errors are the same whether or not the declaration is empty". +// This function implements all the checks that are done on qualifiers regardless of if the +// declaration is empty. +void TParseContext::declarationQualifierErrorCheck(const sh::TQualifier qualifier, + const sh::TLayoutQualifier &layoutQualifier, + const TSourceLoc &location) +{ + if (qualifier == EvqShared && !layoutQualifier.isEmpty()) + { + error(location, "Shared memory declarations cannot have layout specified", "layout"); + } + + if (layoutQualifier.matrixPacking != EmpUnspecified) + { + error(location, "layout qualifier only valid for interface blocks", + getMatrixPackingString(layoutQualifier.matrixPacking)); + return; + } + + if (layoutQualifier.blockStorage != EbsUnspecified) + { + error(location, "layout qualifier only valid for interface blocks", + getBlockStorageString(layoutQualifier.blockStorage)); + return; + } + + if (qualifier == EvqFragmentOut) + { + if (layoutQualifier.location != -1 && layoutQualifier.yuv == true) + { + error(location, "invalid layout qualifier combination", "yuv"); + return; + } + } else - type->setQualifier(paramQualifier); + { + checkYuvIsNotSpecified(location, layoutQualifier.yuv); + } - return false; + // If multiview extension is enabled, "in" qualifier is allowed in the vertex shader in previous + // parsing steps. So it needs to be checked here. + if (isExtensionEnabled(TExtension::OVR_multiview) && mShaderVersion < 300 && + qualifier == EvqVertexIn) + { + error(location, "storage qualifier supported in GLSL ES 3.00 and above only", "in"); + } + + bool canHaveLocation = qualifier == EvqVertexIn || qualifier == EvqFragmentOut; + if (mShaderVersion >= 310) + { + canHaveLocation = canHaveLocation || qualifier == EvqUniform || IsVarying(qualifier); + // We're not checking whether the uniform location is in range here since that depends on + // the type of the variable. + // The type can only be fully determined for non-empty declarations. + } + if (!canHaveLocation) + { + checkLocationIsNotSpecified(location, layoutQualifier); + } } -bool TParseContext::extensionErrorCheck(const TSourceLoc &line, const TString &extension) +void TParseContext::atomicCounterQualifierErrorCheck(const TPublicType &publicType, + const TSourceLoc &location) { - const TExtensionBehavior &extBehavior = extensionBehavior(); - TExtensionBehavior::const_iterator iter = extBehavior.find(extension.c_str()); - if (iter == extBehavior.end()) + if (publicType.precision != EbpHigh) { - error(line, "extension", extension.c_str(), "is not supported"); - return true; + error(location, "Can only be highp", "atomic counter"); } - // In GLSL ES, an extension's default behavior is "disable". - if (iter->second == EBhDisable || iter->second == EBhUndefined) + // dEQP enforces compile error if location is specified. See uniform_location.test. + if (publicType.layoutQualifier.location != -1) { - error(line, "extension", extension.c_str(), "is disabled"); - return true; + error(location, "location must not be set for atomic_uint", "layout"); } - if (iter->second == EBhWarn) + if (publicType.layoutQualifier.binding == -1) { - warning(line, "extension", extension.c_str(), "is being used"); - return false; + error(location, "no binding specified", "atomic counter"); } +} - return false; +void TParseContext::emptyDeclarationErrorCheck(const TType &type, const TSourceLoc &location) +{ + if (type.isUnsizedArray()) + { + // ESSL3 spec section 4.1.9: Array declaration which leaves the size unspecified is an + // error. It is assumed that this applies to empty declarations as well. + error(location, "empty array declaration needs to specify a size", ""); + } } -// These checks are common for all declarations starting a declarator list, and declarators that -// follow an empty declaration. -// -bool TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType, - const TSourceLoc &identifierLocation) +// These checks are done for all declarations that are non-empty. They're done for non-empty +// declarations starting a declarator list, and declarators that follow an empty declaration. +void TParseContext::nonEmptyDeclarationErrorCheck(const TPublicType &publicType, + const TSourceLoc &identifierLocation) { switch (publicType.qualifier) { @@ -1010,105 +1375,363 @@ bool TParseContext::singleDeclarationErrorCheck(const TPublicType &publicType, case EvqAttribute: case EvqVertexIn: case EvqFragmentOut: - if (publicType.type == EbtStruct) + case EvqComputeIn: + if (publicType.getBasicType() == EbtStruct) { error(identifierLocation, "cannot be used with a structure", getQualifierString(publicType.qualifier)); - return true; + return; } - + break; + case EvqBuffer: + if (publicType.getBasicType() != EbtInterfaceBlock) + { + error(identifierLocation, + "cannot declare buffer variables at global scope(outside a block)", + getQualifierString(publicType.qualifier)); + return; + } + break; default: break; } - + std::string reason(getBasicString(publicType.getBasicType())); + reason += "s must be uniform"; if (publicType.qualifier != EvqUniform && - samplerErrorCheck(identifierLocation, publicType, "samplers must be uniform")) + !checkIsNotOpaqueType(identifierLocation, publicType.typeSpecifierNonArray, reason.c_str())) { - return true; + return; + } + + if ((publicType.qualifier != EvqTemporary && publicType.qualifier != EvqGlobal && + publicType.qualifier != EvqConst) && + publicType.getBasicType() == EbtYuvCscStandardEXT) + { + error(identifierLocation, "cannot be used with a yuvCscStandardEXT", + getQualifierString(publicType.qualifier)); + return; + } + + if (mShaderVersion >= 310 && publicType.qualifier == EvqUniform) + { + // Valid uniform declarations can't be unsized arrays since uniforms can't be initialized. + // But invalid shaders may still reach here with an unsized array declaration. + TType type(publicType); + if (!type.isUnsizedArray()) + { + checkUniformLocationInRange(identifierLocation, type.getLocationCount(), + publicType.layoutQualifier); + } } // check for layout qualifier issues const TLayoutQualifier layoutQualifier = publicType.layoutQualifier; - if (layoutQualifier.matrixPacking != EmpUnspecified) + if (IsImage(publicType.getBasicType())) { - error(identifierLocation, "layout qualifier", - getMatrixPackingString(layoutQualifier.matrixPacking), - "only valid for interface blocks"); - return true; + + switch (layoutQualifier.imageInternalFormat) + { + case EiifRGBA32F: + case EiifRGBA16F: + case EiifR32F: + case EiifRGBA8: + case EiifRGBA8_SNORM: + if (!IsFloatImage(publicType.getBasicType())) + { + error(identifierLocation, + "internal image format requires a floating image type", + getBasicString(publicType.getBasicType())); + return; + } + break; + case EiifRGBA32I: + case EiifRGBA16I: + case EiifRGBA8I: + case EiifR32I: + if (!IsIntegerImage(publicType.getBasicType())) + { + error(identifierLocation, + "internal image format requires an integer image type", + getBasicString(publicType.getBasicType())); + return; + } + break; + case EiifRGBA32UI: + case EiifRGBA16UI: + case EiifRGBA8UI: + case EiifR32UI: + if (!IsUnsignedImage(publicType.getBasicType())) + { + error(identifierLocation, + "internal image format requires an unsigned image type", + getBasicString(publicType.getBasicType())); + return; + } + break; + case EiifUnspecified: + error(identifierLocation, "layout qualifier", "No image internal format specified"); + return; + default: + error(identifierLocation, "layout qualifier", "unrecognized token"); + return; + } + + // GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers + switch (layoutQualifier.imageInternalFormat) + { + case EiifR32F: + case EiifR32I: + case EiifR32UI: + break; + default: + if (!publicType.memoryQualifier.readonly && !publicType.memoryQualifier.writeonly) + { + error(identifierLocation, "layout qualifier", + "Except for images with the r32f, r32i and r32ui format qualifiers, " + "image variables must be qualified readonly and/or writeonly"); + return; + } + break; + } + } + else + { + checkInternalFormatIsNotSpecified(identifierLocation, layoutQualifier.imageInternalFormat); + checkMemoryQualifierIsNotSpecified(publicType.memoryQualifier, identifierLocation); } - if (layoutQualifier.blockStorage != EbsUnspecified) + if (IsAtomicCounter(publicType.getBasicType())) { - error(identifierLocation, "layout qualifier", - getBlockStorageString(layoutQualifier.blockStorage), - "only valid for interface blocks"); - return true; + atomicCounterQualifierErrorCheck(publicType, identifierLocation); } + else + { + checkOffsetIsNotSpecified(identifierLocation, layoutQualifier.offset); + } +} - if (publicType.qualifier != EvqVertexIn && publicType.qualifier != EvqFragmentOut && - layoutLocationErrorCheck(identifierLocation, publicType.layoutQualifier)) +void TParseContext::checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type) +{ + TLayoutQualifier layoutQualifier = type.getLayoutQualifier(); + // Note that the ESSL 3.10 section 4.4.5 is not particularly clear on how the binding qualifier + // on arrays of arrays should be handled. We interpret the spec so that the binding value is + // incremented for each element of the innermost nested arrays. This is in line with how arrays + // of arrays of blocks are specified to behave in GLSL 4.50 and a conservative interpretation + // when it comes to which shaders are accepted by the compiler. + int arrayTotalElementCount = type.getArraySizeProduct(); + if (IsImage(type.getBasicType())) { - return true; + checkImageBindingIsValid(identifierLocation, layoutQualifier.binding, + arrayTotalElementCount); + } + else if (IsSampler(type.getBasicType())) + { + checkSamplerBindingIsValid(identifierLocation, layoutQualifier.binding, + arrayTotalElementCount); + } + else if (IsAtomicCounter(type.getBasicType())) + { + checkAtomicCounterBindingIsValid(identifierLocation, layoutQualifier.binding); + } + else + { + ASSERT(!IsOpaqueType(type.getBasicType())); + checkBindingIsNotSpecified(identifierLocation, layoutQualifier.binding); } +} - return false; +void TParseContext::checkLayoutQualifierSupported(const TSourceLoc &location, + const TString &layoutQualifierName, + int versionRequired) +{ + + if (mShaderVersion < versionRequired) + { + error(location, "invalid layout qualifier: not supported", layoutQualifierName.c_str()); + } } -bool TParseContext::layoutLocationErrorCheck(const TSourceLoc &location, - const TLayoutQualifier &layoutQualifier) +bool TParseContext::checkWorkGroupSizeIsNotSpecified(const TSourceLoc &location, + const TLayoutQualifier &layoutQualifier) { - if (layoutQualifier.location != -1) + const sh::WorkGroupSize &localSize = layoutQualifier.localSize; + for (size_t i = 0u; i < localSize.size(); ++i) { - error(location, "invalid layout qualifier:", "location", - "only valid on program inputs and outputs"); - return true; + if (localSize[i] != -1) + { + error(location, + "invalid layout qualifier: only valid when used with 'in' in a compute shader " + "global layout declaration", + getWorkGroupSizeString(i)); + return false; + } } - return false; + return true; +} + +void TParseContext::checkInternalFormatIsNotSpecified(const TSourceLoc &location, + TLayoutImageInternalFormat internalFormat) +{ + if (internalFormat != EiifUnspecified) + { + error(location, "invalid layout qualifier: only valid when used with images", + getImageInternalFormatString(internalFormat)); + } +} + +void TParseContext::checkBindingIsNotSpecified(const TSourceLoc &location, int binding) +{ + if (binding != -1) + { + error(location, + "invalid layout qualifier: only valid when used with opaque types or blocks", + "binding"); + } +} + +void TParseContext::checkOffsetIsNotSpecified(const TSourceLoc &location, int offset) +{ + if (offset != -1) + { + error(location, "invalid layout qualifier: only valid when used with atomic counters", + "offset"); + } +} + +void TParseContext::checkImageBindingIsValid(const TSourceLoc &location, + int binding, + int arrayTotalElementCount) +{ + // Expects arraySize to be 1 when setting binding for only a single variable. + if (binding >= 0 && binding + arrayTotalElementCount > mMaxImageUnits) + { + error(location, "image binding greater than gl_MaxImageUnits", "binding"); + } +} + +void TParseContext::checkSamplerBindingIsValid(const TSourceLoc &location, + int binding, + int arrayTotalElementCount) +{ + // Expects arraySize to be 1 when setting binding for only a single variable. + if (binding >= 0 && binding + arrayTotalElementCount > mMaxCombinedTextureImageUnits) + { + error(location, "sampler binding greater than maximum texture units", "binding"); + } +} + +void TParseContext::checkBlockBindingIsValid(const TSourceLoc &location, + const TQualifier &qualifier, + int binding, + int arraySize) +{ + int size = (arraySize == 0 ? 1 : arraySize); + if (qualifier == EvqUniform) + { + if (binding + size > mMaxUniformBufferBindings) + { + error(location, "uniform block binding greater than MAX_UNIFORM_BUFFER_BINDINGS", + "binding"); + } + } + else if (qualifier == EvqBuffer) + { + if (binding + size > mMaxShaderStorageBufferBindings) + { + error(location, + "shader storage block binding greater than MAX_SHADER_STORAGE_BUFFER_BINDINGS", + "binding"); + } + } +} +void TParseContext::checkAtomicCounterBindingIsValid(const TSourceLoc &location, int binding) +{ + if (binding >= mMaxAtomicCounterBindings) + { + error(location, "atomic counter binding greater than gl_MaxAtomicCounterBindings", + "binding"); + } +} + +void TParseContext::checkUniformLocationInRange(const TSourceLoc &location, + int objectLocationCount, + const TLayoutQualifier &layoutQualifier) +{ + int loc = layoutQualifier.location; + if (loc >= 0 && loc + objectLocationCount > mMaxUniformLocations) + { + error(location, "Uniform location out of range", "location"); + } +} + +void TParseContext::checkYuvIsNotSpecified(const TSourceLoc &location, bool yuv) +{ + if (yuv != false) + { + error(location, "invalid layout qualifier: only valid on program outputs", "yuv"); + } } -bool TParseContext::functionCallLValueErrorCheck(const TFunction *fnCandidate, - TIntermAggregate *aggregate) +void TParseContext::functionCallRValueLValueErrorCheck(const TFunction *fnCandidate, + TIntermAggregate *fnCall) { for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) { TQualifier qual = fnCandidate->getParam(i).type->getQualifier(); + TIntermTyped *argument = (*(fnCall->getSequence()))[i]->getAsTyped(); + if (!IsImage(argument->getBasicType()) && (IsQualifierUnspecified(qual) || qual == EvqIn || + qual == EvqInOut || qual == EvqConstReadOnly)) + { + if (argument->getMemoryQualifier().writeonly) + { + error(argument->getLine(), + "Writeonly value cannot be passed for 'in' or 'inout' parameters.", + fnCall->getFunctionSymbolInfo()->getName().c_str()); + return; + } + } if (qual == EvqOut || qual == EvqInOut) { - TIntermTyped *node = (*(aggregate->getSequence()))[i]->getAsTyped(); - if (lValueErrorCheck(node->getLine(), "assign", node)) + if (!checkCanBeLValue(argument->getLine(), "assign", argument)) { - error(node->getLine(), - "Constant value cannot be passed for 'out' or 'inout' parameters.", "Error"); - recover(); - return true; + error(argument->getLine(), + "Constant value cannot be passed for 'out' or 'inout' parameters.", + fnCall->getFunctionSymbolInfo()->getName().c_str()); + return; } } } - return false; } -void TParseContext::es3InvariantErrorCheck(const TQualifier qualifier, - const TSourceLoc &invariantLocation) +void TParseContext::checkInvariantVariableQualifier(bool invariant, + const TQualifier qualifier, + const TSourceLoc &invariantLocation) { - if (!sh::IsVaryingOut(qualifier) && qualifier != EvqFragmentOut) + if (!invariant) + return; + + if (mShaderVersion < 300) { - error(invariantLocation, "Only out variables can be invariant.", "invariant"); - recover(); + // input variables in the fragment shader can be also qualified as invariant + if (!sh::CanBeInvariantESSL1(qualifier)) + { + error(invariantLocation, "Cannot be qualified as invariant.", "invariant"); + } + } + else + { + if (!sh::CanBeInvariantESSL3OrGreater(qualifier)) + { + error(invariantLocation, "Cannot be qualified as invariant.", "invariant"); + } } } -bool TParseContext::supportsExtension(const char *extension) -{ - const TExtensionBehavior &extbehavior = extensionBehavior(); - TExtensionBehavior::const_iterator iter = extbehavior.find(extension); - return (iter != extbehavior.end()); -} - -bool TParseContext::isExtensionEnabled(const char *extension) const +bool TParseContext::isExtensionEnabled(TExtension extension) const { - return ::IsExtensionEnabled(extensionBehavior(), extension); + return IsExtensionEnabled(extensionBehavior(), extension); } void TParseContext::handleExtensionDirective(const TSourceLoc &loc, @@ -1132,6 +1755,32 @@ void TParseContext::handlePragmaDirective(const TSourceLoc &loc, mDirectiveHandler.handlePragma(srcLoc, name, value, stdgl); } +sh::WorkGroupSize TParseContext::getComputeShaderLocalSize() const +{ + sh::WorkGroupSize result(-1); + for (size_t i = 0u; i < result.size(); ++i) + { + if (mComputeShaderLocalSizeDeclared && mComputeShaderLocalSize[i] == -1) + { + result[i] = 1; + } + else + { + result[i] = mComputeShaderLocalSize[i]; + } + } + return result; +} + +TIntermConstantUnion *TParseContext::addScalarLiteral(const TConstantUnion *constantUnion, + const TSourceLoc &line) +{ + TIntermConstantUnion *node = new TIntermConstantUnion( + constantUnion, TType(constantUnion->getType(), EbpUndefined, EvqConst)); + node->setLine(line); + return node; +} + ///////////////////////////////////////////////////////////////////////////////// // // Non-Errors. @@ -1142,70 +1791,64 @@ const TVariable *TParseContext::getNamedVariable(const TSourceLoc &location, const TString *name, const TSymbol *symbol) { - const TVariable *variable = NULL; - if (!symbol) { error(location, "undeclared identifier", name->c_str()); - recover(); + return nullptr; } - else if (!symbol->isVariable()) + + if (!symbol->isVariable()) { error(location, "variable expected", name->c_str()); - recover(); + return nullptr; } - else - { - variable = static_cast<const TVariable *>(symbol); - if (symbolTable.findBuiltIn(variable->getName(), mShaderVersion) && - !variable->getExtension().empty() && - extensionErrorCheck(location, variable->getExtension())) - { - recover(); - } + const TVariable *variable = static_cast<const TVariable *>(symbol); - // Reject shaders using both gl_FragData and gl_FragColor - TQualifier qualifier = variable->getType().getQualifier(); - if (qualifier == EvqFragData || qualifier == EvqSecondaryFragDataEXT) - { - mUsesFragData = true; - } - else if (qualifier == EvqFragColor || qualifier == EvqSecondaryFragColorEXT) - { - mUsesFragColor = true; - } - if (qualifier == EvqSecondaryFragDataEXT || qualifier == EvqSecondaryFragColorEXT) - { - mUsesSecondaryOutputs = true; - } + if (variable->getExtension() != TExtension::UNDEFINED) + { + checkCanUseExtension(location, variable->getExtension()); + } + + // Reject shaders using both gl_FragData and gl_FragColor + TQualifier qualifier = variable->getType().getQualifier(); + if (qualifier == EvqFragData || qualifier == EvqSecondaryFragDataEXT) + { + mUsesFragData = true; + } + else if (qualifier == EvqFragColor || qualifier == EvqSecondaryFragColorEXT) + { + mUsesFragColor = true; + } + if (qualifier == EvqSecondaryFragDataEXT || qualifier == EvqSecondaryFragColorEXT) + { + mUsesSecondaryOutputs = true; + } - // This validation is not quite correct - it's only an error to write to - // both FragData and FragColor. For simplicity, and because users shouldn't - // be rewarded for reading from undefined varaibles, return an error - // if they are both referenced, rather than assigned. - if (mUsesFragData && mUsesFragColor) + // This validation is not quite correct - it's only an error to write to + // both FragData and FragColor. For simplicity, and because users shouldn't + // be rewarded for reading from undefined varaibles, return an error + // if they are both referenced, rather than assigned. + if (mUsesFragData && mUsesFragColor) + { + const char *errorMessage = "cannot use both gl_FragData and gl_FragColor"; + if (mUsesSecondaryOutputs) { - const char *errorMessage = "cannot use both gl_FragData and gl_FragColor"; - if (mUsesSecondaryOutputs) - { - errorMessage = - "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT)" - " and (gl_FragColor, gl_SecondaryFragColorEXT)"; - } - error(location, errorMessage, name->c_str()); - recover(); + errorMessage = + "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT)" + " and (gl_FragColor, gl_SecondaryFragColorEXT)"; } + error(location, errorMessage, name->c_str()); } - if (!variable) + // GLSL ES 3.1 Revision 4, 7.1.3 Compute Shader Special Variables + if (getShaderType() == GL_COMPUTE_SHADER && !mComputeShaderLocalSizeDeclared && + qualifier == EvqWorkGroupSize) { - TType type(EbtFloat, EbpUndefined); - TVariable *fakeVariable = new TVariable(name, type); - symbolTable.declare(fakeVariable); - variable = fakeVariable; + error(location, + "It is an error to use gl_WorkGroupSize before declaring the local group size", + "gl_WorkGroupSize"); } - return variable; } @@ -1215,75 +1858,82 @@ TIntermTyped *TParseContext::parseVariableIdentifier(const TSourceLoc &location, { const TVariable *variable = getNamedVariable(location, name, symbol); + if (!variable) + { + TIntermTyped *node = CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst)); + node->setLine(location); + return node; + } + + const TType &variableType = variable->getType(); + TIntermTyped *node = nullptr; + if (variable->getConstPointer()) { const TConstantUnion *constArray = variable->getConstPointer(); - return intermediate.addConstantUnion(constArray, variable->getType(), location); + node = new TIntermConstantUnion(constArray, variableType); } - else + else if (variableType.getQualifier() == EvqWorkGroupSize && mComputeShaderLocalSizeDeclared) { - return intermediate.addSymbol(variable->getUniqueId(), variable->getName(), - variable->getType(), location); - } -} + // gl_WorkGroupSize can be used to size arrays according to the ESSL 3.10.4 spec, so it + // needs to be added to the AST as a constant and not as a symbol. + sh::WorkGroupSize workGroupSize = getComputeShaderLocalSize(); + TConstantUnion *constArray = new TConstantUnion[3]; + for (size_t i = 0; i < 3; ++i) + { + constArray[i].setUConst(static_cast<unsigned int>(workGroupSize[i])); + } -// -// Look up a function name in the symbol table, and make sure it is a function. -// -// Return the function symbol if found, otherwise 0. -// -const TFunction *TParseContext::findFunction(const TSourceLoc &line, - TFunction *call, - int inputShaderVersion, - bool *builtIn) -{ - // First find by unmangled name to check whether the function name has been - // hidden by a variable name or struct typename. - // If a function is found, check for one with a matching argument list. - const TSymbol *symbol = symbolTable.find(call->getName(), inputShaderVersion, builtIn); - if (symbol == 0 || symbol->isFunction()) - { - symbol = symbolTable.find(call->getMangledName(), inputShaderVersion, builtIn); - } + ASSERT(variableType.getBasicType() == EbtUInt); + ASSERT(variableType.getObjectSize() == 3); - if (symbol == 0) - { - error(line, "no matching overloaded function found", call->getName().c_str()); - return 0; + TType type(variableType); + type.setQualifier(EvqConst); + node = new TIntermConstantUnion(constArray, type); } + else if ((mGeometryShaderInputPrimitiveType != EptUndefined) && + (variableType.getQualifier() == EvqPerVertexIn)) + { + ASSERT(mGeometryShaderInputArraySize > 0u); - if (!symbol->isFunction()) + node = new TIntermSymbol(variable->getUniqueId(), variable->getName(), variableType); + node->getTypePointer()->sizeOutermostUnsizedArray(mGeometryShaderInputArraySize); + } + else { - error(line, "function name expected", call->getName().c_str()); - return 0; + node = new TIntermSymbol(variable->getUniqueId(), variable->getName(), variableType); } - - return static_cast<const TFunction *>(symbol); + ASSERT(node != nullptr); + node->setLine(location); + return node; } -// // Initializers show up in several places in the grammar. Have one set of // code to handle them here. // -// Returns true on error, false if no error -// +// Returns true on success. bool TParseContext::executeInitializer(const TSourceLoc &line, const TString &identifier, - const TPublicType &pType, + TType type, TIntermTyped *initializer, - TIntermNode **intermNode) + TIntermBinary **initNode) { - ASSERT(intermNode != nullptr); - TType type = TType(pType); + ASSERT(initNode != nullptr); + ASSERT(*initNode == nullptr); TVariable *variable = nullptr; if (type.isUnsizedArray()) { - type.setArraySize(initializer->getArraySize()); + // In case initializer is not an array or type has more dimensions than initializer, this + // will default to setting array sizes to 1. We have not checked yet whether the initializer + // actually is an array or not. Having a non-array initializer for an unsized array will + // result in an error later, so we don't generate an error message here. + auto *arraySizes = initializer->getType().getArraySizes(); + type.sizeUnsizedArrays(arraySizes); } if (!declareVariable(line, identifier, type, &variable)) { - return true; + return false; } bool globalInitWarning = false; @@ -1293,7 +1943,7 @@ bool TParseContext::executeInitializer(const TSourceLoc &line, // Error message does not completely match behavior with ESSL 1.00, but // we want to steer developers towards only using constant expressions. error(line, "global variable initializers must be constant expressions", "="); - return true; + return false; } if (globalInitWarning) { @@ -1312,7 +1962,7 @@ bool TParseContext::executeInitializer(const TSourceLoc &line, { error(line, " cannot initialize this type of qualifier ", variable->getType().getQualifierString()); - return true; + return false; } // // test for and propagate constant @@ -1322,19 +1972,20 @@ bool TParseContext::executeInitializer(const TSourceLoc &line, { if (qualifier != initializer->getType().getQualifier()) { - std::stringstream extraInfoStream; - extraInfoStream << "'" << variable->getType().getCompleteString() << "'"; - std::string extraInfo = extraInfoStream.str(); - error(line, " assigning non-constant to", "=", extraInfo.c_str()); + std::stringstream reasonStream; + reasonStream << "assigning non-constant to '" << variable->getType().getCompleteString() + << "'"; + std::string reason = reasonStream.str(); + error(line, reason.c_str(), "="); variable->getType().setQualifier(EvqTemporary); - return true; + return false; } if (type != initializer->getType()) { error(line, " non-matching types for const initializer ", variable->getType().getQualifierString()); variable->getType().setQualifier(EvqTemporary); - return true; + return false; } // Save the constant folded value to the variable if possible. For example array @@ -1345,8 +1996,8 @@ bool TParseContext::executeInitializer(const TSourceLoc &line, if (initializer->getAsConstantUnion()) { variable->shareConstPointer(initializer->getAsConstantUnion()->getUnionArrayPointer()); - *intermNode = nullptr; - return false; + ASSERT(*initNode == nullptr); + return true; } else if (initializer->getAsSymbolNode()) { @@ -1358,84 +2009,220 @@ bool TParseContext::executeInitializer(const TSourceLoc &line, if (constArray) { variable->shareConstPointer(constArray); - *intermNode = nullptr; - return false; + ASSERT(*initNode == nullptr); + return true; } } } - TIntermSymbol *intermSymbol = intermediate.addSymbol( - variable->getUniqueId(), variable->getName(), variable->getType(), line); - *intermNode = createAssign(EOpInitialize, intermSymbol, initializer, line); - if (*intermNode == nullptr) + TIntermSymbol *intermSymbol = + new TIntermSymbol(variable->getUniqueId(), variable->getName(), variable->getType()); + intermSymbol->setLine(line); + *initNode = createAssign(EOpInitialize, intermSymbol, initializer, line); + if (*initNode == nullptr) { assignError(line, "=", intermSymbol->getCompleteString(), initializer->getCompleteString()); - return true; + return false; } - return false; + return true; +} + +TIntermNode *TParseContext::addConditionInitializer(const TPublicType &pType, + const TString &identifier, + TIntermTyped *initializer, + const TSourceLoc &loc) +{ + checkIsScalarBool(loc, pType); + TIntermBinary *initNode = nullptr; + TType type(pType); + if (executeInitializer(loc, identifier, type, initializer, &initNode)) + { + // The initializer is valid. The init condition needs to have a node - either the + // initializer node, or a constant node in case the initialized variable is const and won't + // be recorded in the AST. + if (initNode == nullptr) + { + return initializer; + } + else + { + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->appendDeclarator(initNode); + return declaration; + } + } + return nullptr; +} + +TIntermNode *TParseContext::addLoop(TLoopType type, + TIntermNode *init, + TIntermNode *cond, + TIntermTyped *expr, + TIntermNode *body, + const TSourceLoc &line) +{ + TIntermNode *node = nullptr; + TIntermTyped *typedCond = nullptr; + if (cond) + { + typedCond = cond->getAsTyped(); + } + if (cond == nullptr || typedCond) + { + if (type == ELoopDoWhile) + { + checkIsScalarBool(line, typedCond); + } + // In the case of other loops, it was checked before that the condition is a scalar boolean. + ASSERT(mDiagnostics->numErrors() > 0 || typedCond == nullptr || + (typedCond->getBasicType() == EbtBool && !typedCond->isArray() && + !typedCond->isVector())); + + node = new TIntermLoop(type, init, typedCond, expr, EnsureBlock(body)); + node->setLine(line); + return node; + } + + ASSERT(type != ELoopDoWhile); + + TIntermDeclaration *declaration = cond->getAsDeclarationNode(); + ASSERT(declaration); + TIntermBinary *declarator = declaration->getSequence()->front()->getAsBinaryNode(); + ASSERT(declarator->getLeft()->getAsSymbolNode()); + + // The condition is a declaration. In the AST representation we don't support declarations as + // loop conditions. Wrap the loop to a block that declares the condition variable and contains + // the loop. + TIntermBlock *block = new TIntermBlock(); + + TIntermDeclaration *declareCondition = new TIntermDeclaration(); + declareCondition->appendDeclarator(declarator->getLeft()->deepCopy()); + block->appendStatement(declareCondition); + + TIntermBinary *conditionInit = new TIntermBinary(EOpAssign, declarator->getLeft()->deepCopy(), + declarator->getRight()->deepCopy()); + TIntermLoop *loop = new TIntermLoop(type, init, conditionInit, expr, EnsureBlock(body)); + block->appendStatement(loop); + loop->setLine(line); + block->setLine(line); + return block; +} + +TIntermNode *TParseContext::addIfElse(TIntermTyped *cond, + TIntermNodePair code, + const TSourceLoc &loc) +{ + bool isScalarBool = checkIsScalarBool(loc, cond); + + // For compile time constant conditions, prune the code now. + if (isScalarBool && cond->getAsConstantUnion()) + { + if (cond->getAsConstantUnion()->getBConst(0) == true) + { + return EnsureBlock(code.node1); + } + else + { + return EnsureBlock(code.node2); + } + } + + TIntermIfElse *node = new TIntermIfElse(cond, EnsureBlock(code.node1), EnsureBlock(code.node2)); + node->setLine(loc); + + return node; +} + +void TParseContext::addFullySpecifiedType(TPublicType *typeSpecifier) +{ + checkPrecisionSpecified(typeSpecifier->getLine(), typeSpecifier->precision, + typeSpecifier->getBasicType()); + + if (mShaderVersion < 300 && typeSpecifier->isArray()) + { + error(typeSpecifier->getLine(), "not supported", "first-class array"); + typeSpecifier->clearArrayness(); + } } -TPublicType TParseContext::addFullySpecifiedType(TQualifier qualifier, - bool invariant, - TLayoutQualifier layoutQualifier, +TPublicType TParseContext::addFullySpecifiedType(const TTypeQualifierBuilder &typeQualifierBuilder, const TPublicType &typeSpecifier) { + TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics); + TPublicType returnType = typeSpecifier; - returnType.qualifier = qualifier; - returnType.invariant = invariant; - returnType.layoutQualifier = layoutQualifier; + returnType.qualifier = typeQualifier.qualifier; + returnType.invariant = typeQualifier.invariant; + returnType.layoutQualifier = typeQualifier.layoutQualifier; + returnType.memoryQualifier = typeQualifier.memoryQualifier; + returnType.precision = typeSpecifier.precision; + + if (typeQualifier.precision != EbpUndefined) + { + returnType.precision = typeQualifier.precision; + } + + checkPrecisionSpecified(typeSpecifier.getLine(), returnType.precision, + typeSpecifier.getBasicType()); + + checkInvariantVariableQualifier(returnType.invariant, returnType.qualifier, + typeSpecifier.getLine()); + + checkWorkGroupSizeIsNotSpecified(typeSpecifier.getLine(), returnType.layoutQualifier); if (mShaderVersion < 300) { - if (typeSpecifier.array) + if (typeSpecifier.isArray()) { - error(typeSpecifier.line, "not supported", "first-class array"); - recover(); + error(typeSpecifier.getLine(), "not supported", "first-class array"); returnType.clearArrayness(); } - if (qualifier == EvqAttribute && - (typeSpecifier.type == EbtBool || typeSpecifier.type == EbtInt)) + if (returnType.qualifier == EvqAttribute && + (typeSpecifier.getBasicType() == EbtBool || typeSpecifier.getBasicType() == EbtInt)) { - error(typeSpecifier.line, "cannot be bool or int", getQualifierString(qualifier)); - recover(); + error(typeSpecifier.getLine(), "cannot be bool or int", + getQualifierString(returnType.qualifier)); } - if ((qualifier == EvqVaryingIn || qualifier == EvqVaryingOut) && - (typeSpecifier.type == EbtBool || typeSpecifier.type == EbtInt)) + if ((returnType.qualifier == EvqVaryingIn || returnType.qualifier == EvqVaryingOut) && + (typeSpecifier.getBasicType() == EbtBool || typeSpecifier.getBasicType() == EbtInt)) { - error(typeSpecifier.line, "cannot be bool or int", getQualifierString(qualifier)); - recover(); + error(typeSpecifier.getLine(), "cannot be bool or int", + getQualifierString(returnType.qualifier)); } } else { - if (!layoutQualifier.isEmpty()) + if (!returnType.layoutQualifier.isEmpty()) { - if (globalErrorCheck(typeSpecifier.line, symbolTable.atGlobalLevel(), "layout")) - { - recover(); - } + checkIsAtGlobalLevel(typeSpecifier.getLine(), "layout"); } - if (sh::IsVarying(qualifier) || qualifier == EvqVertexIn || qualifier == EvqFragmentOut) + if (sh::IsVarying(returnType.qualifier) || returnType.qualifier == EvqVertexIn || + returnType.qualifier == EvqFragmentOut) { - es3InputOutputTypeCheck(qualifier, typeSpecifier, typeSpecifier.line); + checkInputOutputTypeIsValidES3(returnType.qualifier, typeSpecifier, + typeSpecifier.getLine()); + } + if (returnType.qualifier == EvqComputeIn) + { + error(typeSpecifier.getLine(), "'in' can be only used to specify the local group size", + "in"); } } return returnType; } -void TParseContext::es3InputOutputTypeCheck(const TQualifier qualifier, - const TPublicType &type, - const TSourceLoc &qualifierLocation) +void TParseContext::checkInputOutputTypeIsValidES3(const TQualifier qualifier, + const TPublicType &type, + const TSourceLoc &qualifierLocation) { // An input/output variable can never be bool or a sampler. Samplers are checked elsewhere. - if (type.type == EbtBool) + if (type.getBasicType() == EbtBool) { error(qualifierLocation, "cannot be bool", getQualifierString(qualifier)); - recover(); } // Specific restrictions apply for vertex shader inputs and fragment shader outputs. @@ -1443,21 +2230,19 @@ void TParseContext::es3InputOutputTypeCheck(const TQualifier qualifier, { case EvqVertexIn: // ESSL 3.00 section 4.3.4 - if (type.array) + if (type.isArray()) { error(qualifierLocation, "cannot be array", getQualifierString(qualifier)); - recover(); } - // Vertex inputs with a struct type are disallowed in singleDeclarationErrorCheck + // Vertex inputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck return; case EvqFragmentOut: // ESSL 3.00 section 4.3.6 - if (type.isMatrix()) + if (type.typeSpecifierNonArray.isMatrix()) { error(qualifierLocation, "cannot be matrix", getQualifierString(qualifier)); - recover(); } - // Fragment outputs with a struct type are disallowed in singleDeclarationErrorCheck + // Fragment outputs with a struct type are disallowed in nonEmptyDeclarationErrorCheck return; default: break; @@ -1466,506 +2251,997 @@ void TParseContext::es3InputOutputTypeCheck(const TQualifier qualifier, // Vertex shader outputs / fragment shader inputs have a different, slightly more lenient set of // restrictions. bool typeContainsIntegers = - (type.type == EbtInt || type.type == EbtUInt || type.isStructureContainingType(EbtInt) || - type.isStructureContainingType(EbtUInt)); + (type.getBasicType() == EbtInt || type.getBasicType() == EbtUInt || + type.isStructureContainingType(EbtInt) || type.isStructureContainingType(EbtUInt)); if (typeContainsIntegers && qualifier != EvqFlatIn && qualifier != EvqFlatOut) { error(qualifierLocation, "must use 'flat' interpolation here", getQualifierString(qualifier)); - recover(); } - if (type.type == EbtStruct) + if (type.getBasicType() == EbtStruct) { // ESSL 3.00 sections 4.3.4 and 4.3.6. // These restrictions are only implied by the ESSL 3.00 spec, but // the ESSL 3.10 spec lists these restrictions explicitly. - if (type.array) + if (type.isArray()) { error(qualifierLocation, "cannot be an array of structures", getQualifierString(qualifier)); - recover(); } if (type.isStructureContainingArrays()) { error(qualifierLocation, "cannot be a structure containing an array", getQualifierString(qualifier)); - recover(); } if (type.isStructureContainingType(EbtStruct)) { error(qualifierLocation, "cannot be a structure containing a structure", getQualifierString(qualifier)); - recover(); } if (type.isStructureContainingType(EbtBool)) { error(qualifierLocation, "cannot be a structure containing a bool", getQualifierString(qualifier)); - recover(); } } } -TIntermAggregate *TParseContext::parseSingleDeclaration(TPublicType &publicType, - const TSourceLoc &identifierOrTypeLocation, - const TString &identifier) +void TParseContext::checkLocalVariableConstStorageQualifier(const TQualifierWrapperBase &qualifier) { - TIntermSymbol *symbol = - intermediate.addSymbol(0, identifier, TType(publicType), identifierOrTypeLocation); + if (qualifier.getType() == QtStorage) + { + const TStorageQualifierWrapper &storageQualifier = + static_cast<const TStorageQualifierWrapper &>(qualifier); + if (!declaringFunction() && storageQualifier.getQualifier() != EvqConst && + !symbolTable.atGlobalLevel()) + { + error(storageQualifier.getLine(), + "Local variables can only use the const storage qualifier.", + storageQualifier.getQualifierString().c_str()); + } + } +} - bool emptyDeclaration = (identifier == ""); +void TParseContext::checkMemoryQualifierIsNotSpecified(const TMemoryQualifier &memoryQualifier, + const TSourceLoc &location) +{ + const std::string reason( + "Only allowed with shader storage blocks, variables declared within shader storage blocks " + "and variables declared as image types."); + if (memoryQualifier.readonly) + { + error(location, reason.c_str(), "readonly"); + } + if (memoryQualifier.writeonly) + { + error(location, reason.c_str(), "writeonly"); + } + if (memoryQualifier.coherent) + { + error(location, reason.c_str(), "coherent"); + } + if (memoryQualifier.restrictQualifier) + { + error(location, reason.c_str(), "restrict"); + } + if (memoryQualifier.volatileQualifier) + { + error(location, reason.c_str(), "volatile"); + } +} + +// Make sure there is no offset overlapping, and store the newly assigned offset to "type" in +// intermediate tree. +void TParseContext::checkAtomicCounterOffsetDoesNotOverlap(bool forceAppend, + const TSourceLoc &loc, + TType *type) +{ + if (!IsAtomicCounter(type->getBasicType())) + { + return; + } + + const size_t size = type->isArray() ? kAtomicCounterArrayStride * type->getArraySizeProduct() + : kAtomicCounterSize; + TLayoutQualifier layoutQualifier = type->getLayoutQualifier(); + auto &bindingState = mAtomicCounterBindingStates[layoutQualifier.binding]; + int offset; + if (layoutQualifier.offset == -1 || forceAppend) + { + offset = bindingState.appendSpan(size); + } + else + { + offset = bindingState.insertSpan(layoutQualifier.offset, size); + } + if (offset == -1) + { + error(loc, "Offset overlapping", "atomic counter"); + return; + } + layoutQualifier.offset = offset; + type->setLayoutQualifier(layoutQualifier); +} + +void TParseContext::checkGeometryShaderInputAndSetArraySize(const TSourceLoc &location, + const char *token, + TType *type) +{ + if (IsGeometryShaderInput(mShaderType, type->getQualifier())) + { + if (type->isArray() && type->getOutermostArraySize() == 0u) + { + // Set size for the unsized geometry shader inputs if they are declared after a valid + // input primitive declaration. + if (mGeometryShaderInputPrimitiveType != EptUndefined) + { + ASSERT(mGeometryShaderInputArraySize > 0u); + type->sizeOutermostUnsizedArray(mGeometryShaderInputArraySize); + } + else + { + // [GLSL ES 3.2 SPEC Chapter 4.4.1.2] + // An input can be declared without an array size if there is a previous layout + // which specifies the size. + error(location, + "Missing a valid input primitive declaration before declaring an unsized " + "array input", + token); + } + } + else if (type->isArray()) + { + setGeometryShaderInputArraySize(type->getOutermostArraySize(), location); + } + else + { + error(location, "Geometry shader input variable must be declared as an array", token); + } + } +} + +TIntermDeclaration *TParseContext::parseSingleDeclaration( + TPublicType &publicType, + const TSourceLoc &identifierOrTypeLocation, + const TString &identifier) +{ + TType type(publicType); + if ((mCompileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) && + mDirectiveHandler.pragma().stdgl.invariantAll) + { + TQualifier qualifier = type.getQualifier(); + + // The directive handler has already taken care of rejecting invalid uses of this pragma + // (for example, in ESSL 3.00 fragment shaders), so at this point, flatten it into all + // affected variable declarations: + // + // 1. Built-in special variables which are inputs to the fragment shader. (These are handled + // elsewhere, in TranslatorGLSL.) + // + // 2. Outputs from vertex shaders in ESSL 1.00 and 3.00 (EvqVaryingOut and EvqVertexOut). It + // is actually less likely that there will be bugs in the handling of ESSL 3.00 shaders, but + // the way this is currently implemented we have to enable this compiler option before + // parsing the shader and determining the shading language version it uses. If this were + // implemented as a post-pass, the workaround could be more targeted. + // + // 3. Inputs in ESSL 1.00 fragment shaders (EvqVaryingIn). This is somewhat in violation of + // the specification, but there are desktop OpenGL drivers that expect that this is the + // behavior of the #pragma when specified in ESSL 1.00 fragment shaders. + if (qualifier == EvqVaryingOut || qualifier == EvqVertexOut || qualifier == EvqVaryingIn) + { + type.setInvariant(true); + } + } - mDeferredSingleDeclarationErrorCheck = emptyDeclaration; + checkGeometryShaderInputAndSetArraySize(identifierOrTypeLocation, identifier.c_str(), &type); + declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier, + identifierOrTypeLocation); + + bool emptyDeclaration = (identifier == ""); + mDeferredNonEmptyDeclarationErrorCheck = emptyDeclaration; + + TIntermSymbol *symbol = nullptr; if (emptyDeclaration) { - if (publicType.isUnsizedArray()) + emptyDeclarationErrorCheck(type, identifierOrTypeLocation); + // In most cases we don't need to create a symbol node for an empty declaration. + // But if the empty declaration is declaring a struct type, the symbol node will store that. + if (type.getBasicType() == EbtStruct) { - // ESSL3 spec section 4.1.9: Array declaration which leaves the size unspecified is an - // error. It is assumed that this applies to empty declarations as well. - error(identifierOrTypeLocation, "empty array declaration needs to specify a size", - identifier.c_str()); + symbol = new TIntermSymbol(symbolTable.getEmptySymbolId(), "", type); + } + else if (IsAtomicCounter(publicType.getBasicType())) + { + setAtomicCounterBindingDefaultOffset(publicType, identifierOrTypeLocation); } } else { - if (singleDeclarationErrorCheck(publicType, identifierOrTypeLocation)) - recover(); + nonEmptyDeclarationErrorCheck(publicType, identifierOrTypeLocation); - if (nonInitErrorCheck(identifierOrTypeLocation, identifier, &publicType)) - recover(); + checkCanBeDeclaredWithoutInitializer(identifierOrTypeLocation, identifier, &type); + + checkAtomicCounterOffsetDoesNotOverlap(false, identifierOrTypeLocation, &type); TVariable *variable = nullptr; - if (!declareVariable(identifierOrTypeLocation, identifier, TType(publicType), &variable)) - recover(); + declareVariable(identifierOrTypeLocation, identifier, type, &variable); - if (variable && symbol) - symbol->setId(variable->getUniqueId()); + if (variable) + { + symbol = new TIntermSymbol(variable->getUniqueId(), identifier, type); + } } - return intermediate.makeAggregate(symbol, identifierOrTypeLocation); + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->setLine(identifierOrTypeLocation); + if (symbol) + { + symbol->setLine(identifierOrTypeLocation); + declaration->appendDeclarator(symbol); + } + return declaration; } -TIntermAggregate *TParseContext::parseSingleArrayDeclaration(TPublicType &publicType, - const TSourceLoc &identifierLocation, - const TString &identifier, - const TSourceLoc &indexLocation, - TIntermTyped *indexExpression) +TIntermDeclaration *TParseContext::parseSingleArrayDeclaration( + TPublicType &elementType, + const TSourceLoc &identifierLocation, + const TString &identifier, + const TSourceLoc &indexLocation, + const TVector<unsigned int> &arraySizes) { - mDeferredSingleDeclarationErrorCheck = false; + mDeferredNonEmptyDeclarationErrorCheck = false; - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); + declarationQualifierErrorCheck(elementType.qualifier, elementType.layoutQualifier, + identifierLocation); - if (nonInitErrorCheck(identifierLocation, identifier, &publicType)) - recover(); + nonEmptyDeclarationErrorCheck(elementType, identifierLocation); - if (arrayTypeErrorCheck(indexLocation, publicType) || - arrayQualifierErrorCheck(indexLocation, publicType)) - { - recover(); - } + checkIsValidTypeAndQualifierForArray(indexLocation, elementType); - TType arrayType(publicType); + TType arrayType(elementType); + arrayType.makeArrays(arraySizes); - int size; - if (arraySizeErrorCheck(identifierLocation, indexExpression, size)) - { - recover(); - } - // Make the type an array even if size check failed. - // This ensures useless error messages regarding the variable's non-arrayness won't follow. - arrayType.setArraySize(size); + checkGeometryShaderInputAndSetArraySize(indexLocation, identifier.c_str(), &arrayType); + + checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, &arrayType); + + checkAtomicCounterOffsetDoesNotOverlap(false, identifierLocation, &arrayType); TVariable *variable = nullptr; - if (!declareVariable(identifierLocation, identifier, arrayType, &variable)) - recover(); + declareVariable(identifierLocation, identifier, arrayType, &variable); - TIntermSymbol *symbol = intermediate.addSymbol(0, identifier, arrayType, identifierLocation); - if (variable && symbol) - symbol->setId(variable->getUniqueId()); + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->setLine(identifierLocation); - return intermediate.makeAggregate(symbol, identifierLocation); + if (variable) + { + TIntermSymbol *symbol = new TIntermSymbol(variable->getUniqueId(), identifier, arrayType); + symbol->setLine(identifierLocation); + declaration->appendDeclarator(symbol); + } + + return declaration; } -TIntermAggregate *TParseContext::parseSingleInitDeclaration(const TPublicType &publicType, - const TSourceLoc &identifierLocation, - const TString &identifier, - const TSourceLoc &initLocation, - TIntermTyped *initializer) +TIntermDeclaration *TParseContext::parseSingleInitDeclaration(const TPublicType &publicType, + const TSourceLoc &identifierLocation, + const TString &identifier, + const TSourceLoc &initLocation, + TIntermTyped *initializer) { - mDeferredSingleDeclarationErrorCheck = false; + mDeferredNonEmptyDeclarationErrorCheck = false; - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); + declarationQualifierErrorCheck(publicType.qualifier, publicType.layoutQualifier, + identifierLocation); - TIntermNode *intermNode = nullptr; - if (!executeInitializer(identifierLocation, identifier, publicType, initializer, &intermNode)) - { - // - // Build intermediate representation - // - return intermNode ? intermediate.makeAggregate(intermNode, initLocation) : nullptr; - } - else + nonEmptyDeclarationErrorCheck(publicType, identifierLocation); + + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->setLine(identifierLocation); + + TIntermBinary *initNode = nullptr; + TType type(publicType); + if (executeInitializer(identifierLocation, identifier, type, initializer, &initNode)) { - recover(); - return nullptr; + if (initNode) + { + declaration->appendDeclarator(initNode); + } } + return declaration; } -TIntermAggregate *TParseContext::parseSingleArrayInitDeclaration( - TPublicType &publicType, +TIntermDeclaration *TParseContext::parseSingleArrayInitDeclaration( + TPublicType &elementType, const TSourceLoc &identifierLocation, const TString &identifier, const TSourceLoc &indexLocation, - TIntermTyped *indexExpression, + const TVector<unsigned int> &arraySizes, const TSourceLoc &initLocation, TIntermTyped *initializer) { - mDeferredSingleDeclarationErrorCheck = false; + mDeferredNonEmptyDeclarationErrorCheck = false; - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); + declarationQualifierErrorCheck(elementType.qualifier, elementType.layoutQualifier, + identifierLocation); - if (arrayTypeErrorCheck(indexLocation, publicType) || - arrayQualifierErrorCheck(indexLocation, publicType)) - { - recover(); - } + nonEmptyDeclarationErrorCheck(elementType, identifierLocation); - TPublicType arrayType(publicType); + checkIsValidTypeAndQualifierForArray(indexLocation, elementType); - int size = 0; - // If indexExpression is nullptr, then the array will eventually get its size implicitly from - // the initializer. - if (indexExpression != nullptr && - arraySizeErrorCheck(identifierLocation, indexExpression, size)) - { - recover(); - } - // Make the type an array even if size check failed. - // This ensures useless error messages regarding the variable's non-arrayness won't follow. - arrayType.setArraySize(size); + TType arrayType(elementType); + arrayType.makeArrays(arraySizes); + + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->setLine(identifierLocation); // initNode will correspond to the whole of "type b[n] = initializer". - TIntermNode *initNode = nullptr; - if (!executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode)) + TIntermBinary *initNode = nullptr; + if (executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode)) { - return initNode ? intermediate.makeAggregate(initNode, initLocation) : nullptr; - } - else - { - recover(); - return nullptr; + if (initNode) + { + declaration->appendDeclarator(initNode); + } } + + return declaration; } -TIntermAggregate *TParseContext::parseInvariantDeclaration(const TSourceLoc &invariantLoc, - const TSourceLoc &identifierLoc, - const TString *identifier, - const TSymbol *symbol) +TIntermInvariantDeclaration *TParseContext::parseInvariantDeclaration( + const TTypeQualifierBuilder &typeQualifierBuilder, + const TSourceLoc &identifierLoc, + const TString *identifier, + const TSymbol *symbol) { - // invariant declaration - if (globalErrorCheck(invariantLoc, symbolTable.atGlobalLevel(), "invariant varying")) + TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics); + + if (!typeQualifier.invariant) { - recover(); + error(identifierLoc, "Expected invariant", identifier->c_str()); + return nullptr; + } + if (!checkIsAtGlobalLevel(identifierLoc, "invariant varying")) + { + return nullptr; } - if (!symbol) { error(identifierLoc, "undeclared identifier declared as invariant", identifier->c_str()); - recover(); return nullptr; } - else + if (!IsQualifierUnspecified(typeQualifier.qualifier)) { - const TString kGlFrontFacing("gl_FrontFacing"); - if (*identifier == kGlFrontFacing) - { - error(identifierLoc, "identifier should not be declared as invariant", - identifier->c_str()); - recover(); - return nullptr; - } - symbolTable.addInvariantVarying(std::string(identifier->c_str())); - const TVariable *variable = getNamedVariable(identifierLoc, identifier, symbol); - ASSERT(variable); - const TType &type = variable->getType(); - TIntermSymbol *intermSymbol = - intermediate.addSymbol(variable->getUniqueId(), *identifier, type, identifierLoc); + error(identifierLoc, "invariant declaration specifies qualifier", + getQualifierString(typeQualifier.qualifier)); + } + if (typeQualifier.precision != EbpUndefined) + { + error(identifierLoc, "invariant declaration specifies precision", + getPrecisionString(typeQualifier.precision)); + } + if (!typeQualifier.layoutQualifier.isEmpty()) + { + error(identifierLoc, "invariant declaration specifies layout", "'layout'"); + } - TIntermAggregate *aggregate = intermediate.makeAggregate(intermSymbol, identifierLoc); - aggregate->setOp(EOpInvariantDeclaration); - return aggregate; + const TVariable *variable = getNamedVariable(identifierLoc, identifier, symbol); + if (!variable) + { + return nullptr; } + const TType &type = variable->getType(); + + checkInvariantVariableQualifier(typeQualifier.invariant, type.getQualifier(), + typeQualifier.line); + checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line); + + symbolTable.addInvariantVarying(std::string(identifier->c_str())); + + TIntermSymbol *intermSymbol = new TIntermSymbol(variable->getUniqueId(), *identifier, type); + intermSymbol->setLine(identifierLoc); + + return new TIntermInvariantDeclaration(intermSymbol, identifierLoc); } -TIntermAggregate *TParseContext::parseDeclarator(TPublicType &publicType, - TIntermAggregate *aggregateDeclaration, - const TSourceLoc &identifierLocation, - const TString &identifier) +void TParseContext::parseDeclarator(TPublicType &publicType, + const TSourceLoc &identifierLocation, + const TString &identifier, + TIntermDeclaration *declarationOut) { // If the declaration starting this declarator list was empty (example: int,), some checks were // not performed. - if (mDeferredSingleDeclarationErrorCheck) + if (mDeferredNonEmptyDeclarationErrorCheck) { - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); - mDeferredSingleDeclarationErrorCheck = false; + nonEmptyDeclarationErrorCheck(publicType, identifierLocation); + mDeferredNonEmptyDeclarationErrorCheck = false; } - if (locationDeclaratorListCheck(identifierLocation, publicType)) - recover(); - - if (nonInitErrorCheck(identifierLocation, identifier, &publicType)) - recover(); + checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); TVariable *variable = nullptr; - if (!declareVariable(identifierLocation, identifier, TType(publicType), &variable)) - recover(); + TType type(publicType); - TIntermSymbol *symbol = - intermediate.addSymbol(0, identifier, TType(publicType), identifierLocation); - if (variable && symbol) - symbol->setId(variable->getUniqueId()); + checkGeometryShaderInputAndSetArraySize(identifierLocation, identifier.c_str(), &type); - return intermediate.growAggregate(aggregateDeclaration, symbol, identifierLocation); + checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, &type); + + checkAtomicCounterOffsetDoesNotOverlap(true, identifierLocation, &type); + + declareVariable(identifierLocation, identifier, type, &variable); + + if (variable) + { + TIntermSymbol *symbol = new TIntermSymbol(variable->getUniqueId(), identifier, type); + symbol->setLine(identifierLocation); + declarationOut->appendDeclarator(symbol); + } } -TIntermAggregate *TParseContext::parseArrayDeclarator(TPublicType &publicType, - TIntermAggregate *aggregateDeclaration, - const TSourceLoc &identifierLocation, - const TString &identifier, - const TSourceLoc &arrayLocation, - TIntermTyped *indexExpression) +void TParseContext::parseArrayDeclarator(TPublicType &elementType, + const TSourceLoc &identifierLocation, + const TString &identifier, + const TSourceLoc &arrayLocation, + const TVector<unsigned int> &arraySizes, + TIntermDeclaration *declarationOut) { // If the declaration starting this declarator list was empty (example: int,), some checks were // not performed. - if (mDeferredSingleDeclarationErrorCheck) + if (mDeferredNonEmptyDeclarationErrorCheck) { - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); - mDeferredSingleDeclarationErrorCheck = false; + nonEmptyDeclarationErrorCheck(elementType, identifierLocation); + mDeferredNonEmptyDeclarationErrorCheck = false; } - if (locationDeclaratorListCheck(identifierLocation, publicType)) - recover(); - - if (nonInitErrorCheck(identifierLocation, identifier, &publicType)) - recover(); + checkDeclaratorLocationIsNotSpecified(identifierLocation, elementType); - if (arrayTypeErrorCheck(arrayLocation, publicType) || - arrayQualifierErrorCheck(arrayLocation, publicType)) - { - recover(); - } - else + if (checkIsValidTypeAndQualifierForArray(arrayLocation, elementType)) { - TType arrayType = TType(publicType); - int size; - if (arraySizeErrorCheck(arrayLocation, indexExpression, size)) - { - recover(); - } - arrayType.setArraySize(size); + TType arrayType(elementType); + arrayType.makeArrays(arraySizes); - TVariable *variable = nullptr; - if (!declareVariable(identifierLocation, identifier, arrayType, &variable)) - recover(); + checkGeometryShaderInputAndSetArraySize(identifierLocation, identifier.c_str(), &arrayType); - TIntermSymbol *symbol = - intermediate.addSymbol(0, identifier, arrayType, identifierLocation); - if (variable && symbol) - symbol->setId(variable->getUniqueId()); + checkCanBeDeclaredWithoutInitializer(identifierLocation, identifier, &arrayType); - return intermediate.growAggregate(aggregateDeclaration, symbol, identifierLocation); - } + checkAtomicCounterOffsetDoesNotOverlap(true, identifierLocation, &arrayType); - return nullptr; + TVariable *variable = nullptr; + declareVariable(identifierLocation, identifier, arrayType, &variable); + + if (variable) + { + TIntermSymbol *symbol = + new TIntermSymbol(variable->getUniqueId(), identifier, arrayType); + symbol->setLine(identifierLocation); + declarationOut->appendDeclarator(symbol); + } + } } -TIntermAggregate *TParseContext::parseInitDeclarator(const TPublicType &publicType, - TIntermAggregate *aggregateDeclaration, - const TSourceLoc &identifierLocation, - const TString &identifier, - const TSourceLoc &initLocation, - TIntermTyped *initializer) +void TParseContext::parseInitDeclarator(const TPublicType &publicType, + const TSourceLoc &identifierLocation, + const TString &identifier, + const TSourceLoc &initLocation, + TIntermTyped *initializer, + TIntermDeclaration *declarationOut) { // If the declaration starting this declarator list was empty (example: int,), some checks were // not performed. - if (mDeferredSingleDeclarationErrorCheck) + if (mDeferredNonEmptyDeclarationErrorCheck) { - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); - mDeferredSingleDeclarationErrorCheck = false; + nonEmptyDeclarationErrorCheck(publicType, identifierLocation); + mDeferredNonEmptyDeclarationErrorCheck = false; } - if (locationDeclaratorListCheck(identifierLocation, publicType)) - recover(); + checkDeclaratorLocationIsNotSpecified(identifierLocation, publicType); - TIntermNode *intermNode = nullptr; - if (!executeInitializer(identifierLocation, identifier, publicType, initializer, &intermNode)) + TIntermBinary *initNode = nullptr; + TType type(publicType); + if (executeInitializer(identifierLocation, identifier, type, initializer, &initNode)) { // // build the intermediate representation // - if (intermNode) + if (initNode) { - return intermediate.growAggregate(aggregateDeclaration, intermNode, initLocation); + declarationOut->appendDeclarator(initNode); } - else + } +} + +void TParseContext::parseArrayInitDeclarator(const TPublicType &elementType, + const TSourceLoc &identifierLocation, + const TString &identifier, + const TSourceLoc &indexLocation, + const TVector<unsigned int> &arraySizes, + const TSourceLoc &initLocation, + TIntermTyped *initializer, + TIntermDeclaration *declarationOut) +{ + // If the declaration starting this declarator list was empty (example: int,), some checks were + // not performed. + if (mDeferredNonEmptyDeclarationErrorCheck) + { + nonEmptyDeclarationErrorCheck(elementType, identifierLocation); + mDeferredNonEmptyDeclarationErrorCheck = false; + } + + checkDeclaratorLocationIsNotSpecified(identifierLocation, elementType); + + checkIsValidTypeAndQualifierForArray(indexLocation, elementType); + + TType arrayType(elementType); + arrayType.makeArrays(arraySizes); + + // initNode will correspond to the whole of "b[n] = initializer". + TIntermBinary *initNode = nullptr; + if (executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode)) + { + if (initNode) { - return aggregateDeclaration; + declarationOut->appendDeclarator(initNode); } } - else +} + +TIntermNode *TParseContext::addEmptyStatement(const TSourceLoc &location) +{ + // It's simpler to parse an empty statement as a constant expression rather than having a + // different type of node just for empty statements, that will be pruned from the AST anyway. + TIntermNode *node = CreateZeroNode(TType(EbtInt, EbpMedium)); + node->setLine(location); + return node; +} + +void TParseContext::setAtomicCounterBindingDefaultOffset(const TPublicType &publicType, + const TSourceLoc &location) +{ + const TLayoutQualifier &layoutQualifier = publicType.layoutQualifier; + checkAtomicCounterBindingIsValid(location, layoutQualifier.binding); + if (layoutQualifier.binding == -1 || layoutQualifier.offset == -1) { - recover(); - return nullptr; + error(location, "Requires both binding and offset", "layout"); + return; } + mAtomicCounterBindingStates[layoutQualifier.binding].setDefaultOffset(layoutQualifier.offset); } -TIntermAggregate *TParseContext::parseArrayInitDeclarator(const TPublicType &publicType, - TIntermAggregate *aggregateDeclaration, - const TSourceLoc &identifierLocation, - const TString &identifier, - const TSourceLoc &indexLocation, - TIntermTyped *indexExpression, - const TSourceLoc &initLocation, - TIntermTyped *initializer) +void TParseContext::parseDefaultPrecisionQualifier(const TPrecision precision, + const TPublicType &type, + const TSourceLoc &loc) { - // If the declaration starting this declarator list was empty (example: int,), some checks were - // not performed. - if (mDeferredSingleDeclarationErrorCheck) + if ((precision == EbpHigh) && (getShaderType() == GL_FRAGMENT_SHADER) && + !getFragmentPrecisionHigh()) { - if (singleDeclarationErrorCheck(publicType, identifierLocation)) - recover(); - mDeferredSingleDeclarationErrorCheck = false; + error(loc, "precision is not supported in fragment shader", "highp"); } - if (locationDeclaratorListCheck(identifierLocation, publicType)) - recover(); + if (!CanSetDefaultPrecisionOnType(type)) + { + error(loc, "illegal type argument for default precision qualifier", + getBasicString(type.getBasicType())); + return; + } + symbolTable.setDefaultPrecision(type.getBasicType(), precision); +} - if (arrayTypeErrorCheck(indexLocation, publicType) || - arrayQualifierErrorCheck(indexLocation, publicType)) +bool TParseContext::checkPrimitiveTypeMatchesTypeQualifier(const TTypeQualifier &typeQualifier) +{ + switch (typeQualifier.layoutQualifier.primitiveType) { - recover(); + case EptLines: + case EptLinesAdjacency: + case EptTriangles: + case EptTrianglesAdjacency: + return typeQualifier.qualifier == EvqGeometryIn; + + case EptLineStrip: + case EptTriangleStrip: + return typeQualifier.qualifier == EvqGeometryOut; + + case EptPoints: + return true; + + default: + UNREACHABLE(); + return false; } +} - TPublicType arrayType(publicType); +void TParseContext::setGeometryShaderInputArraySize(unsigned int inputArraySize, + const TSourceLoc &line) +{ + if (mGeometryShaderInputArraySize == 0u) + { + mGeometryShaderInputArraySize = inputArraySize; + } + else if (mGeometryShaderInputArraySize != inputArraySize) + { + error(line, + "Array size or input primitive declaration doesn't match the size of earlier sized " + "array inputs.", + "layout"); + } +} + +bool TParseContext::parseGeometryShaderInputLayoutQualifier(const TTypeQualifier &typeQualifier) +{ + ASSERT(typeQualifier.qualifier == EvqGeometryIn); + + const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier; - int size = 0; - // If indexExpression is nullptr, then the array will eventually get its size implicitly from - // the initializer. - if (indexExpression != nullptr && - arraySizeErrorCheck(identifierLocation, indexExpression, size)) + if (layoutQualifier.maxVertices != -1) { - recover(); + error(typeQualifier.line, + "max_vertices can only be declared in 'out' layout in a geometry shader", "layout"); + return false; } - // Make the type an array even if size check failed. - // This ensures useless error messages regarding the variable's non-arrayness won't follow. - arrayType.setArraySize(size); - // initNode will correspond to the whole of "b[n] = initializer". - TIntermNode *initNode = nullptr; - if (!executeInitializer(identifierLocation, identifier, arrayType, initializer, &initNode)) + // Set mGeometryInputPrimitiveType if exists + if (layoutQualifier.primitiveType != EptUndefined) { - if (initNode) + if (!checkPrimitiveTypeMatchesTypeQualifier(typeQualifier)) + { + error(typeQualifier.line, "invalid primitive type for 'in' layout", "layout"); + return false; + } + + if (mGeometryShaderInputPrimitiveType == EptUndefined) { - return intermediate.growAggregate(aggregateDeclaration, initNode, initLocation); + mGeometryShaderInputPrimitiveType = layoutQualifier.primitiveType; + setGeometryShaderInputArraySize( + GetGeometryShaderInputArraySize(mGeometryShaderInputPrimitiveType), + typeQualifier.line); } - else + else if (mGeometryShaderInputPrimitiveType != layoutQualifier.primitiveType) { - return aggregateDeclaration; + error(typeQualifier.line, "primitive doesn't match earlier input primitive declaration", + "layout"); + return false; } } - else + + // Set mGeometryInvocations if exists + if (layoutQualifier.invocations > 0) { - recover(); - return nullptr; + if (mGeometryShaderInvocations == 0) + { + mGeometryShaderInvocations = layoutQualifier.invocations; + } + else if (mGeometryShaderInvocations != layoutQualifier.invocations) + { + error(typeQualifier.line, "invocations contradicts to the earlier declaration", + "layout"); + return false; + } } + + return true; } -void TParseContext::parseGlobalLayoutQualifier(const TPublicType &typeQualifier) +bool TParseContext::parseGeometryShaderOutputLayoutQualifier(const TTypeQualifier &typeQualifier) { - if (typeQualifier.qualifier != EvqUniform) + ASSERT(typeQualifier.qualifier == EvqGeometryOut); + + const TLayoutQualifier &layoutQualifier = typeQualifier.layoutQualifier; + + if (layoutQualifier.invocations > 0) { - error(typeQualifier.line, "invalid qualifier:", getQualifierString(typeQualifier.qualifier), - "global layout must be uniform"); - recover(); - return; + error(typeQualifier.line, + "invocations can only be declared in 'in' layout in a geometry shader", "layout"); + return false; } + // Set mGeometryOutputPrimitiveType if exists + if (layoutQualifier.primitiveType != EptUndefined) + { + if (!checkPrimitiveTypeMatchesTypeQualifier(typeQualifier)) + { + error(typeQualifier.line, "invalid primitive type for 'out' layout", "layout"); + return false; + } + + if (mGeometryShaderOutputPrimitiveType == EptUndefined) + { + mGeometryShaderOutputPrimitiveType = layoutQualifier.primitiveType; + } + else if (mGeometryShaderOutputPrimitiveType != layoutQualifier.primitiveType) + { + error(typeQualifier.line, + "primitive doesn't match earlier output primitive declaration", "layout"); + return false; + } + } + + // Set mGeometryMaxVertices if exists + if (layoutQualifier.maxVertices > -1) + { + if (mGeometryShaderMaxVertices == -1) + { + mGeometryShaderMaxVertices = layoutQualifier.maxVertices; + } + else if (mGeometryShaderMaxVertices != layoutQualifier.maxVertices) + { + error(typeQualifier.line, "max_vertices contradicts to the earlier declaration", + "layout"); + return false; + } + } + + return true; +} + +void TParseContext::parseGlobalLayoutQualifier(const TTypeQualifierBuilder &typeQualifierBuilder) +{ + TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics); const TLayoutQualifier layoutQualifier = typeQualifier.layoutQualifier; - ASSERT(!layoutQualifier.isEmpty()); - if (mShaderVersion < 300) + checkInvariantVariableQualifier(typeQualifier.invariant, typeQualifier.qualifier, + typeQualifier.line); + + // It should never be the case, but some strange parser errors can send us here. + if (layoutQualifier.isEmpty()) { - error(typeQualifier.line, "layout qualifiers supported in GLSL ES 3.00 only", "layout"); - recover(); + error(typeQualifier.line, "Error during layout qualifier parsing.", "?"); return; } - if (layoutLocationErrorCheck(typeQualifier.line, typeQualifier.layoutQualifier)) + if (!layoutQualifier.isCombinationValid()) { - recover(); + error(typeQualifier.line, "invalid layout qualifier combination", "layout"); return; } - if (layoutQualifier.matrixPacking != EmpUnspecified) + checkBindingIsNotSpecified(typeQualifier.line, layoutQualifier.binding); + + checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line); + + checkInternalFormatIsNotSpecified(typeQualifier.line, layoutQualifier.imageInternalFormat); + + checkYuvIsNotSpecified(typeQualifier.line, layoutQualifier.yuv); + + checkOffsetIsNotSpecified(typeQualifier.line, layoutQualifier.offset); + + checkStd430IsForShaderStorageBlock(typeQualifier.line, layoutQualifier.blockStorage, + typeQualifier.qualifier); + + if (typeQualifier.qualifier == EvqComputeIn) { - mDefaultMatrixPacking = layoutQualifier.matrixPacking; + if (mComputeShaderLocalSizeDeclared && + !layoutQualifier.isLocalSizeEqual(mComputeShaderLocalSize)) + { + error(typeQualifier.line, "Work group size does not match the previous declaration", + "layout"); + return; + } + + if (mShaderVersion < 310) + { + error(typeQualifier.line, "in type qualifier supported in GLSL ES 3.10 only", "layout"); + return; + } + + if (!layoutQualifier.localSize.isAnyValueSet()) + { + error(typeQualifier.line, "No local work group size specified", "layout"); + return; + } + + const TVariable *maxComputeWorkGroupSize = static_cast<const TVariable *>( + symbolTable.findBuiltIn("gl_MaxComputeWorkGroupSize", mShaderVersion)); + + const TConstantUnion *maxComputeWorkGroupSizeData = + maxComputeWorkGroupSize->getConstPointer(); + + for (size_t i = 0u; i < layoutQualifier.localSize.size(); ++i) + { + if (layoutQualifier.localSize[i] != -1) + { + mComputeShaderLocalSize[i] = layoutQualifier.localSize[i]; + const int maxComputeWorkGroupSizeValue = maxComputeWorkGroupSizeData[i].getIConst(); + if (mComputeShaderLocalSize[i] < 1 || + mComputeShaderLocalSize[i] > maxComputeWorkGroupSizeValue) + { + std::stringstream reasonStream; + reasonStream << "invalid value: Value must be at least 1 and no greater than " + << maxComputeWorkGroupSizeValue; + const std::string &reason = reasonStream.str(); + + error(typeQualifier.line, reason.c_str(), getWorkGroupSizeString(i)); + return; + } + } + } + + mComputeShaderLocalSizeDeclared = true; } + else if (typeQualifier.qualifier == EvqGeometryIn) + { + if (mShaderVersion < 310) + { + error(typeQualifier.line, "in type qualifier supported in GLSL ES 3.10 only", "layout"); + return; + } - if (layoutQualifier.blockStorage != EbsUnspecified) + if (!parseGeometryShaderInputLayoutQualifier(typeQualifier)) + { + return; + } + } + else if (typeQualifier.qualifier == EvqGeometryOut) + { + if (mShaderVersion < 310) + { + error(typeQualifier.line, "out type qualifier supported in GLSL ES 3.10 only", + "layout"); + return; + } + + if (!parseGeometryShaderOutputLayoutQualifier(typeQualifier)) + { + return; + } + } + else if (isExtensionEnabled(TExtension::OVR_multiview) && + typeQualifier.qualifier == EvqVertexIn) + { + // This error is only specified in WebGL, but tightens unspecified behavior in the native + // specification. + if (mNumViews != -1 && layoutQualifier.numViews != mNumViews) + { + error(typeQualifier.line, "Number of views does not match the previous declaration", + "layout"); + return; + } + + if (layoutQualifier.numViews == -1) + { + error(typeQualifier.line, "No num_views specified", "layout"); + return; + } + + if (layoutQualifier.numViews > mMaxNumViews) + { + error(typeQualifier.line, "num_views greater than the value of GL_MAX_VIEWS_OVR", + "layout"); + return; + } + + mNumViews = layoutQualifier.numViews; + } + else { - mDefaultBlockStorage = layoutQualifier.blockStorage; + if (!checkWorkGroupSizeIsNotSpecified(typeQualifier.line, layoutQualifier)) + { + return; + } + + if (typeQualifier.qualifier != EvqUniform && typeQualifier.qualifier != EvqBuffer) + { + error(typeQualifier.line, "invalid qualifier: global layout can only be set for blocks", + getQualifierString(typeQualifier.qualifier)); + return; + } + + if (mShaderVersion < 300) + { + error(typeQualifier.line, "layout qualifiers supported in GLSL ES 3.00 and above", + "layout"); + return; + } + + checkLocationIsNotSpecified(typeQualifier.line, layoutQualifier); + + if (layoutQualifier.matrixPacking != EmpUnspecified) + { + if (typeQualifier.qualifier == EvqUniform) + { + mDefaultUniformMatrixPacking = layoutQualifier.matrixPacking; + } + else if (typeQualifier.qualifier == EvqBuffer) + { + mDefaultBufferMatrixPacking = layoutQualifier.matrixPacking; + } + } + + if (layoutQualifier.blockStorage != EbsUnspecified) + { + if (typeQualifier.qualifier == EvqUniform) + { + mDefaultUniformBlockStorage = layoutQualifier.blockStorage; + } + else if (typeQualifier.qualifier == EvqBuffer) + { + mDefaultBufferBlockStorage = layoutQualifier.blockStorage; + } + } } } -TIntermAggregate *TParseContext::addFunctionPrototypeDeclaration(const TFunction &function, - const TSourceLoc &location) +TIntermFunctionPrototype *TParseContext::createPrototypeNodeFromFunction( + const TFunction &function, + const TSourceLoc &location, + bool insertParametersToSymbolTable) { - // Note: symbolTableFunction could be the same as function if this is the first declaration. - // Either way the instance in the symbol table is used to track whether the function is declared - // multiple times. - TFunction *symbolTableFunction = - static_cast<TFunction *>(symbolTable.find(function.getMangledName(), getShaderVersion())); - if (symbolTableFunction->hasPrototypeDeclaration() && mShaderVersion == 100) - { - // ESSL 1.00.17 section 4.2.7. - // Doesn't apply to ESSL 3.00.4: see section 4.2.3. - error(location, "duplicate function prototype declarations are not allowed", "function"); - recover(); - } - symbolTableFunction->setHasPrototypeDeclaration(); + checkIsNotReserved(location, function.getName()); - TIntermAggregate *prototype = new TIntermAggregate; - prototype->setType(function.getReturnType()); - prototype->setName(function.getMangledName()); - prototype->setFunctionId(function.getUniqueId()); + TIntermFunctionPrototype *prototype = + new TIntermFunctionPrototype(function.getReturnType(), TSymbolUniqueId(function)); + // TODO(oetuaho@nvidia.com): Instead of converting the function information here, the node could + // point to the data that already exists in the symbol table. + prototype->getFunctionSymbolInfo()->setFromFunction(function); + prototype->setLine(location); for (size_t i = 0; i < function.getParamCount(); i++) { const TConstParameter ¶m = function.getParam(i); - if (param.name != 0) - { - TVariable variable(param.name, *param.type); - TIntermSymbol *paramSymbol = intermediate.addSymbol( - variable.getUniqueId(), variable.getName(), variable.getType(), location); - prototype = intermediate.growAggregate(prototype, paramSymbol, location); + TIntermSymbol *symbol = nullptr; + + // If the parameter has no name, it's not an error, just don't add it to symbol table (could + // be used for unused args). + if (param.name != nullptr) + { + // Insert the parameter in the symbol table. + if (insertParametersToSymbolTable) + { + TVariable *variable = symbolTable.declareVariable(param.name, *param.type); + if (variable) + { + symbol = new TIntermSymbol(variable->getUniqueId(), variable->getName(), + variable->getType()); + } + else + { + error(location, "redefinition", param.name->c_str()); + } + } + // Unsized type of a named parameter should have already been checked and sanitized. + ASSERT(!param.type->isUnsizedArray()); } else { - TIntermSymbol *paramSymbol = intermediate.addSymbol(0, "", *param.type, location); - prototype = intermediate.growAggregate(prototype, paramSymbol, location); + if (param.type->isUnsizedArray()) + { + error(location, "function parameter array must be sized at compile time", "[]"); + // We don't need to size the arrays since the parameter is unnamed and hence + // inaccessible. + } } + if (!symbol) + { + // The parameter had no name or declaring the symbol failed - either way, add a nameless + // symbol. + symbol = new TIntermSymbol(symbolTable.getEmptySymbolId(), "", *param.type); + } + symbol->setLine(location); + prototype->appendParameter(symbol); + } + return prototype; +} + +TIntermFunctionPrototype *TParseContext::addFunctionPrototypeDeclaration( + const TFunction &parsedFunction, + const TSourceLoc &location) +{ + // Note: function found from the symbol table could be the same as parsedFunction if this is the + // first declaration. Either way the instance in the symbol table is used to track whether the + // function is declared multiple times. + TFunction *function = static_cast<TFunction *>( + symbolTable.find(parsedFunction.getMangledName(), getShaderVersion())); + if (function->hasPrototypeDeclaration() && mShaderVersion == 100) + { + // ESSL 1.00.17 section 4.2.7. + // Doesn't apply to ESSL 3.00.4: see section 4.2.3. + error(location, "duplicate function prototype declarations are not allowed", "function"); } + function->setHasPrototypeDeclaration(); - prototype->setOp(EOpPrototype); + TIntermFunctionPrototype *prototype = + createPrototypeNodeFromFunction(*function, location, false); symbolTable.pop(); @@ -1973,135 +3249,80 @@ TIntermAggregate *TParseContext::addFunctionPrototypeDeclaration(const TFunction { // ESSL 3.00.4 section 4.2.4. error(location, "local function prototype declarations are not allowed", "function"); - recover(); } return prototype; } -TIntermAggregate *TParseContext::addFunctionDefinition(const TFunction &function, - TIntermAggregate *functionPrototype, - TIntermAggregate *functionBody, - const TSourceLoc &location) +TIntermFunctionDefinition *TParseContext::addFunctionDefinition( + TIntermFunctionPrototype *functionPrototype, + TIntermBlock *functionBody, + const TSourceLoc &location) { - //?? Check that all paths return a value if return type != void ? - // May be best done as post process phase on intermediate code + // Check that non-void functions have at least one return statement. if (mCurrentFunctionType->getBasicType() != EbtVoid && !mFunctionReturnsValue) { - error(location, "function does not return a value:", "", function.getName().c_str()); - recover(); + error(location, "function does not return a value:", + functionPrototype->getFunctionSymbolInfo()->getName().c_str()); } - TIntermAggregate *aggregate = - intermediate.growAggregate(functionPrototype, functionBody, location); - intermediate.setAggregateOperator(aggregate, EOpFunction, location); - aggregate->setName(function.getMangledName().c_str()); - aggregate->setType(function.getReturnType()); - aggregate->setFunctionId(function.getUniqueId()); + if (functionBody == nullptr) + { + functionBody = new TIntermBlock(); + functionBody->setLine(location); + } + TIntermFunctionDefinition *functionNode = + new TIntermFunctionDefinition(functionPrototype, functionBody); + functionNode->setLine(location); symbolTable.pop(); - return aggregate; + return functionNode; } -void TParseContext::parseFunctionPrototype(const TSourceLoc &location, - TFunction *function, - TIntermAggregate **aggregateOut) +void TParseContext::parseFunctionDefinitionHeader(const TSourceLoc &location, + TFunction **function, + TIntermFunctionPrototype **prototypeOut) { + ASSERT(function); + ASSERT(*function); const TSymbol *builtIn = - symbolTable.findBuiltIn(function->getMangledName(), getShaderVersion()); + symbolTable.findBuiltIn((*function)->getMangledName(), getShaderVersion()); if (builtIn) { - error(location, "built-in functions cannot be redefined", function->getName().c_str()); - recover(); + error(location, "built-in functions cannot be redefined", (*function)->getName().c_str()); } - - TFunction *prevDec = - static_cast<TFunction *>(symbolTable.find(function->getMangledName(), getShaderVersion())); - // - // Note: 'prevDec' could be 'function' if this is the first time we've seen function - // as it would have just been put in the symbol table. Otherwise, we're looking up - // an earlier occurance. - // - if (prevDec->isDefined()) + else { - // Then this function already has a body. - error(location, "function already has a body", function->getName().c_str()); - recover(); - } - prevDec->setDefined(); - // - // Overload the unique ID of the definition to be the same unique ID as the declaration. - // Eventually we will probably want to have only a single definition and just swap the - // arguments to be the definition's arguments. - // - function->setUniqueId(prevDec->getUniqueId()); + TFunction *prevDec = static_cast<TFunction *>( + symbolTable.find((*function)->getMangledName(), getShaderVersion())); - // Raise error message if main function takes any parameters or return anything other than void - if (function->getName() == "main") - { - if (function->getParamCount() > 0) + // Note: 'prevDec' could be 'function' if this is the first time we've seen function as it + // would have just been put in the symbol table. Otherwise, we're looking up an earlier + // occurance. + if (*function != prevDec) { - error(location, "function cannot take any parameter(s)", function->getName().c_str()); - recover(); + // Swap the parameters of the previous declaration to the parameters of the function + // definition (parameter names may differ). + prevDec->swapParameters(**function); + + // The function definition will share the same symbol as any previous declaration. + *function = prevDec; } - if (function->getReturnType().getBasicType() != EbtVoid) + + if ((*function)->isDefined()) { - error(location, "", function->getReturnType().getBasicString(), - "main function cannot return a value"); - recover(); + error(location, "function already has a body", (*function)->getName().c_str()); } + + (*function)->setDefined(); } - // - // Remember the return type for later checking for RETURN statements. - // - mCurrentFunctionType = &(prevDec->getReturnType()); + // Remember the return type for later checking for return statements. + mCurrentFunctionType = &((*function)->getReturnType()); mFunctionReturnsValue = false; - // - // Insert parameters into the symbol table. - // If the parameter has no name, it's not an error, just don't insert it - // (could be used for unused args). - // - // Also, accumulate the list of parameters into the HIL, so lower level code - // knows where to find parameters. - // - TIntermAggregate *paramNodes = new TIntermAggregate; - for (size_t i = 0; i < function->getParamCount(); i++) - { - const TConstParameter ¶m = function->getParam(i); - if (param.name != 0) - { - TVariable *variable = new TVariable(param.name, *param.type); - // - // Insert the parameters with name in the symbol table. - // - if (!symbolTable.declare(variable)) - { - error(location, "redefinition", variable->getName().c_str()); - recover(); - paramNodes = intermediate.growAggregate( - paramNodes, intermediate.addSymbol(0, "", *param.type, location), location); - continue; - } - - // - // Add the parameter to the HIL - // - TIntermSymbol *symbol = intermediate.addSymbol( - variable->getUniqueId(), variable->getName(), variable->getType(), location); - - paramNodes = intermediate.growAggregate(paramNodes, symbol, location); - } - else - { - paramNodes = intermediate.growAggregate( - paramNodes, intermediate.addSymbol(0, "", *param.type, location), location); - } - } - intermediate.setAggregateOperator(paramNodes, EOpParameters, location); - *aggregateOut = paramNodes; + *prototypeOut = createPrototypeNodeFromFunction(**function, location, true); setLoopNestingLevel(0); } @@ -2117,22 +3338,42 @@ TFunction *TParseContext::parseFunctionDeclarator(const TSourceLoc &location, TF // TFunction *prevDec = static_cast<TFunction *>(symbolTable.find(function->getMangledName(), getShaderVersion())); - if (prevDec) + + for (size_t i = 0u; i < function->getParamCount(); ++i) + { + auto ¶m = function->getParam(i); + if (param.type->isStructSpecifier()) + { + // ESSL 3.00.6 section 12.10. + error(location, "Function parameter type cannot be a structure definition", + function->getName().c_str()); + } + } + + if (getShaderVersion() >= 300 && + symbolTable.hasUnmangledBuiltInForShaderVersion(function->getName().c_str(), + getShaderVersion())) + { + // With ESSL 3.00 and above, names of built-in functions cannot be redeclared as functions. + // Therefore overloading or redefining builtin functions is an error. + error(location, "Name of a built-in function cannot be redeclared as function", + function->getName().c_str()); + } + else if (prevDec) { if (prevDec->getReturnType() != function->getReturnType()) { - error(location, "overloaded functions must have the same return type", + error(location, "function must have the same return type in all of its declarations", function->getReturnType().getBasicString()); - recover(); } for (size_t i = 0; i < prevDec->getParamCount(); ++i) { if (prevDec->getParam(i).type->getQualifier() != function->getParam(i).type->getQualifier()) { - error(location, "overloaded functions must have the same parameter qualifiers", + error(location, + "function must have the same parameter qualifiers in all of its declarations", function->getParam(i).type->getQualifierString()); - recover(); } } } @@ -2145,22 +3386,33 @@ TFunction *TParseContext::parseFunctionDeclarator(const TSourceLoc &location, TF { if (!prevSym->isFunction()) { - error(location, "redefinition", function->getName().c_str(), "function"); - recover(); + error(location, "redefinition of a function", function->getName().c_str()); } } else { // Insert the unmangled name to detect potential future redefinition as a variable. - TFunction *newFunction = - new TFunction(NewPoolTString(function->getName().c_str()), &function->getReturnType()); - symbolTable.getOuterLevel()->insertUnmangled(newFunction); + symbolTable.getOuterLevel()->insertUnmangled(function); } // We're at the inner scope level of the function's arguments and body statement. // Add the function prototype to the surrounding scope instead. symbolTable.getOuterLevel()->insert(function); + // Raise error message if main function takes any parameters or return anything other than void + if (function->getName() == "main") + { + if (function->getParamCount() > 0) + { + error(location, "function cannot take any parameter(s)", "main"); + } + if (function->getReturnType().getBasicType() != EbtVoid) + { + error(location, "main function cannot return a value", + function->getReturnType().getBasicString()); + } + } + // // If this is a redeclaration, it could also be a definition, in which case, we want to use the // variable names from this one, and not the one that's @@ -2169,420 +3421,302 @@ TFunction *TParseContext::parseFunctionDeclarator(const TSourceLoc &location, TF return function; } -TFunction *TParseContext::addConstructorFunc(const TPublicType &publicTypeIn) +TFunction *TParseContext::parseFunctionHeader(const TPublicType &type, + const TString *name, + const TSourceLoc &location) { - TPublicType publicType = publicTypeIn; - if (publicType.isStructSpecifier) + if (type.qualifier != EvqGlobal && type.qualifier != EvqTemporary) { - error(publicType.line, "constructor can't be a structure definition", - getBasicString(publicType.type)); - recover(); + error(location, "no qualifiers allowed for function return", + getQualifierString(type.qualifier)); } - - TOperator op = EOpNull; - if (publicType.userDef) + if (!type.layoutQualifier.isEmpty()) { - op = EOpConstructStruct; + error(location, "no qualifiers allowed for function return", "layout"); } - else + // make sure an opaque type is not involved as well... + std::string reason(getBasicString(type.getBasicType())); + reason += "s can't be function return values"; + checkIsNotOpaqueType(location, type.typeSpecifierNonArray, reason.c_str()); + if (mShaderVersion < 300) { - switch (publicType.type) - { - case EbtFloat: - if (publicType.isMatrix()) - { - switch (publicType.getCols()) - { - case 2: - switch (publicType.getRows()) - { - case 2: - op = EOpConstructMat2; - break; - case 3: - op = EOpConstructMat2x3; - break; - case 4: - op = EOpConstructMat2x4; - break; - } - break; - case 3: - switch (publicType.getRows()) - { - case 2: - op = EOpConstructMat3x2; - break; - case 3: - op = EOpConstructMat3; - break; - case 4: - op = EOpConstructMat3x4; - break; - } - break; - case 4: - switch (publicType.getRows()) - { - case 2: - op = EOpConstructMat4x2; - break; - case 3: - op = EOpConstructMat4x3; - break; - case 4: - op = EOpConstructMat4; - break; - } - break; - } - } - else - { - switch (publicType.getNominalSize()) - { - case 1: - op = EOpConstructFloat; - break; - case 2: - op = EOpConstructVec2; - break; - case 3: - op = EOpConstructVec3; - break; - case 4: - op = EOpConstructVec4; - break; - } - } - break; - - case EbtInt: - switch (publicType.getNominalSize()) - { - case 1: - op = EOpConstructInt; - break; - case 2: - op = EOpConstructIVec2; - break; - case 3: - op = EOpConstructIVec3; - break; - case 4: - op = EOpConstructIVec4; - break; - } - break; + // Array return values are forbidden, but there's also no valid syntax for declaring array + // return values in ESSL 1.00. + ASSERT(!type.isArray() || mDiagnostics->numErrors() > 0); - case EbtUInt: - switch (publicType.getNominalSize()) - { - case 1: - op = EOpConstructUInt; - break; - case 2: - op = EOpConstructUVec2; - break; - case 3: - op = EOpConstructUVec3; - break; - case 4: - op = EOpConstructUVec4; - break; - } - break; - - case EbtBool: - switch (publicType.getNominalSize()) - { - case 1: - op = EOpConstructBool; - break; - case 2: - op = EOpConstructBVec2; - break; - case 3: - op = EOpConstructBVec3; - break; - case 4: - op = EOpConstructBVec4; - break; - } - break; - - default: - break; - } - - if (op == EOpNull) + if (type.isStructureContainingArrays()) { - error(publicType.line, "cannot construct this type", getBasicString(publicType.type)); - recover(); - publicType.type = EbtFloat; - op = EOpConstructFloat; + // ESSL 1.00.17 section 6.1 Function Definitions + error(location, "structures containing arrays can't be function return values", + TType(type).getCompleteString().c_str()); } } - TString tempString; - const TType *type = new TType(publicType); - return new TFunction(&tempString, type, op); + // Add the function as a prototype after parsing it (we do not support recursion) + return new TFunction(&symbolTable, name, new TType(type)); } -// This function is used to test for the correctness of the parameters passed to various constructor -// functions and also convert them to the right datatype if it is allowed and required. -// -// Returns 0 for an error or the constructed node (aggregate or typed) for no error. -// -TIntermTyped *TParseContext::addConstructor(TIntermNode *arguments, - TType *type, - TOperator op, - TFunction *fnCall, - const TSourceLoc &line) +TFunction *TParseContext::addNonConstructorFunc(const TString *name, const TSourceLoc &loc) { - TIntermAggregate *constructor = arguments->getAsAggregate(); - ASSERT(constructor != nullptr); + const TType *returnType = TCache::getType(EbtVoid, EbpUndefined); + return new TFunction(&symbolTable, name, returnType); +} - if (type->isArray()) - { - // GLSL ES 3.00 section 5.4.4: Each argument must be the same type as the element type of - // the array. - TIntermSequence *args = constructor->getSequence(); - for (size_t i = 0; i < args->size(); i++) - { - const TType &argType = (*args)[i]->getAsTyped()->getType(); - // It has already been checked that the argument is not an array. - ASSERT(!argType.isArray()); - if (!argType.sameElementType(*type)) - { - error(line, "Array constructor argument has an incorrect type", "Error"); - recover(); - return nullptr; - } - } - } - else if (op == EOpConstructStruct) +TFunction *TParseContext::addConstructorFunc(const TPublicType &publicType) +{ + if (mShaderVersion < 300 && publicType.isArray()) { - const TFieldList &fields = type->getStruct()->fields(); - TIntermSequence *args = constructor->getSequence(); - - for (size_t i = 0; i < fields.size(); i++) - { - if (i >= args->size() || (*args)[i]->getAsTyped()->getType() != *fields[i]->type()) - { - error(line, "Structure constructor arguments do not match structure fields", - "Error"); - recover(); - - return 0; - } - } + error(publicType.getLine(), "array constructor supported in GLSL ES 3.00 and above only", + "[]"); } - - // Turn the argument list itself into a constructor - constructor->setOp(op); - constructor->setLine(line); - ASSERT(constructor->isConstructor()); - - // Need to set type before setPrecisionFromChildren() because bool doesn't have precision. - constructor->setType(*type); - - // Structs should not be precision qualified, the individual members may be. - // Built-in types on the other hand should be precision qualified. - if (op != EOpConstructStruct) + if (publicType.isStructSpecifier()) { - constructor->setPrecisionFromChildren(); - type->setPrecision(constructor->getPrecision()); + error(publicType.getLine(), "constructor can't be a structure definition", + getBasicString(publicType.getBasicType())); } - TIntermTyped *constConstructor = intermediate.foldAggregateBuiltIn(constructor); - if (constConstructor) + TType *type = new TType(publicType); + if (!type->canBeConstructed()) { - return constConstructor; + error(publicType.getLine(), "cannot construct this type", + getBasicString(publicType.getBasicType())); + type->setBasicType(EbtFloat); } - return constructor; + return new TFunction(&symbolTable, nullptr, type, EOpConstruct); } -// -// This function returns the tree representation for the vector field(s) being accessed from contant -// vector. -// If only one component of vector is accessed (v.x or v[0] where v is a contant vector), then a -// contant node is returned, else an aggregate node is returned (for v.xy). The input to this -// function could either be the symbol node or it could be the intermediate tree representation of -// accessing fields in a constant structure or column of a constant matrix. -// -TIntermTyped *TParseContext::addConstVectorNode(TVectorFields &fields, - TIntermConstantUnion *node, - const TSourceLoc &line, - bool outOfRangeIndexIsError) +void TParseContext::checkIsNotUnsizedArray(const TSourceLoc &line, + const char *errorMessage, + const char *token, + TType *arrayType) { - const TConstantUnion *unionArray = node->getUnionArrayPointer(); - ASSERT(unionArray); - - TConstantUnion *constArray = new TConstantUnion[fields.num]; - - for (int i = 0; i < fields.num; i++) + if (arrayType->isUnsizedArray()) { - if (fields.offsets[i] >= node->getType().getNominalSize()) - { - std::stringstream extraInfoStream; - extraInfoStream << "vector field selection out of range '" << fields.offsets[i] << "'"; - std::string extraInfo = extraInfoStream.str(); - outOfRangeError(outOfRangeIndexIsError, line, "", "[", extraInfo.c_str()); - fields.offsets[i] = node->getType().getNominalSize() - 1; - } - - constArray[i] = unionArray[fields.offsets[i]]; + error(line, errorMessage, token); + arrayType->sizeUnsizedArrays(nullptr); } - return intermediate.addConstantUnion(constArray, node->getType(), line); } -// -// This function returns the column being accessed from a constant matrix. The values are retrieved -// from the symbol table and parse-tree is built for a vector (each column of a matrix is a vector). -// The input to the function could either be a symbol node (m[0] where m is a constant matrix)that -// represents a constant matrix or it could be the tree representation of the constant matrix -// (s.m1[0] where s is a constant structure) -// -TIntermTyped *TParseContext::addConstMatrixNode(int index, - TIntermConstantUnion *node, - const TSourceLoc &line, - bool outOfRangeIndexIsError) +TParameter TParseContext::parseParameterDeclarator(TType *type, + const TString *name, + const TSourceLoc &nameLoc) { - if (index >= node->getType().getCols()) + ASSERT(type); + checkIsNotUnsizedArray(nameLoc, "function parameter array must specify a size", name->c_str(), + type); + if (type->getBasicType() == EbtVoid) { - std::stringstream extraInfoStream; - extraInfoStream << "matrix field selection out of range '" << index << "'"; - std::string extraInfo = extraInfoStream.str(); - outOfRangeError(outOfRangeIndexIsError, line, "", "[", extraInfo.c_str()); - index = node->getType().getCols() - 1; + error(nameLoc, "illegal use of type 'void'", name->c_str()); } + checkIsNotReserved(nameLoc, *name); + TParameter param = {name, type}; + return param; +} - const TConstantUnion *unionArray = node->getUnionArrayPointer(); - int size = node->getType().getCols(); - return intermediate.addConstantUnion(&unionArray[size * index], node->getType(), line); +TParameter TParseContext::parseParameterDeclarator(const TPublicType &publicType, + const TString *name, + const TSourceLoc &nameLoc) +{ + TType *type = new TType(publicType); + return parseParameterDeclarator(type, name, nameLoc); } -// -// This function returns an element of an array accessed from a constant array. The values are -// retrieved from the symbol table and parse-tree is built for the type of the element. The input -// to the function could either be a symbol node (a[0] where a is a constant array)that represents a -// constant array or it could be the tree representation of the constant array (s.a1[0] where s is a -// constant structure) -// -TIntermTyped *TParseContext::addConstArrayNode(int index, - TIntermConstantUnion *node, - const TSourceLoc &line, - bool outOfRangeIndexIsError) +TParameter TParseContext::parseParameterArrayDeclarator(const TString *name, + const TSourceLoc &nameLoc, + const TVector<unsigned int> &arraySizes, + const TSourceLoc &arrayLoc, + TPublicType *elementType) { - TType arrayElementType = node->getType(); - arrayElementType.clearArrayness(); + checkArrayElementIsNotArray(arrayLoc, *elementType); + TType *arrayType = new TType(*elementType); + arrayType->makeArrays(arraySizes); + return parseParameterDeclarator(arrayType, name, nameLoc); +} - if (index >= node->getType().getArraySize()) +bool TParseContext::checkUnsizedArrayConstructorArgumentDimensionality(TIntermSequence *arguments, + TType type, + const TSourceLoc &line) +{ + if (arguments->empty()) + { + error(line, "implicitly sized array constructor must have at least one argument", "[]"); + return false; + } + for (TIntermNode *arg : *arguments) { - std::stringstream extraInfoStream; - extraInfoStream << "array field selection out of range '" << index << "'"; - std::string extraInfo = extraInfoStream.str(); - outOfRangeError(outOfRangeIndexIsError, line, "", "[", extraInfo.c_str()); - index = node->getType().getArraySize() - 1; + TIntermTyped *element = arg->getAsTyped(); + ASSERT(element); + size_t dimensionalityFromElement = element->getType().getNumArraySizes() + 1u; + if (dimensionalityFromElement > type.getNumArraySizes()) + { + error(line, "constructing from a non-dereferenced array", "constructor"); + return false; + } + else if (dimensionalityFromElement < type.getNumArraySizes()) + { + if (dimensionalityFromElement == 1u) + { + error(line, "implicitly sized array of arrays constructor argument is not an array", + "constructor"); + } + else + { + error(line, + "implicitly sized array of arrays constructor argument dimensionality is too " + "low", + "constructor"); + } + return false; + } } - size_t arrayElementSize = arrayElementType.getObjectSize(); - const TConstantUnion *unionArray = node->getUnionArrayPointer(); - return intermediate.addConstantUnion(&unionArray[arrayElementSize * index], node->getType(), - line); + return true; } +// This function is used to test for the correctness of the parameters passed to various constructor +// functions and also convert them to the right datatype if it is allowed and required. // -// This function returns the value of a particular field inside a constant structure from the symbol -// table. -// 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. +// Returns a node to add to the tree regardless of if an error was generated or not. // -TIntermTyped *TParseContext::addConstStruct(const TString &identifier, - TIntermTyped *node, +TIntermTyped *TParseContext::addConstructor(TIntermSequence *arguments, + TType type, const TSourceLoc &line) { - const TFieldList &fields = node->getType().getStruct()->fields(); - size_t instanceSize = 0; - - for (size_t index = 0; index < fields.size(); ++index) + if (type.isUnsizedArray()) { - if (fields[index]->name() == identifier) + if (!checkUnsizedArrayConstructorArgumentDimensionality(arguments, type, line)) { - break; + type.sizeUnsizedArrays(nullptr); + return CreateZeroNode(type); } - else + TIntermTyped *firstElement = arguments->at(0)->getAsTyped(); + ASSERT(firstElement); + if (type.getOutermostArraySize() == 0u) + { + type.sizeOutermostUnsizedArray(static_cast<unsigned int>(arguments->size())); + } + for (size_t i = 0; i < firstElement->getType().getNumArraySizes(); ++i) { - instanceSize += fields[index]->type()->getObjectSize(); + if ((*type.getArraySizes())[i] == 0u) + { + type.setArraySize(i, (*firstElement->getType().getArraySizes())[i]); + } } + ASSERT(!type.isUnsizedArray()); } - TIntermTyped *typedNode; - TIntermConstantUnion *tempConstantNode = node->getAsConstantUnion(); - if (tempConstantNode) + if (!checkConstructorArguments(line, arguments, type)) { - const TConstantUnion *constArray = tempConstantNode->getUnionArrayPointer(); - - // type will be changed in the calling function - typedNode = intermediate.addConstantUnion(constArray + instanceSize, - tempConstantNode->getType(), line); + return CreateZeroNode(type); } - else - { - error(line, "Cannot offset into the structure", "Error"); - recover(); - return 0; - } + TIntermAggregate *constructorNode = TIntermAggregate::CreateConstructor(type, arguments); + constructorNode->setLine(line); - return typedNode; + // TODO(oetuaho@nvidia.com): Add support for folding array constructors. + if (!constructorNode->isArray()) + { + return constructorNode->fold(mDiagnostics); + } + return constructorNode; } // // Interface/uniform blocks +// TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks. // -TIntermAggregate *TParseContext::addInterfaceBlock(const TPublicType &typeQualifier, - const TSourceLoc &nameLine, - const TString &blockName, - TFieldList *fieldList, - const TString *instanceName, - const TSourceLoc &instanceLine, - TIntermTyped *arrayIndex, - const TSourceLoc &arrayIndexLine) +TIntermDeclaration *TParseContext::addInterfaceBlock( + const TTypeQualifierBuilder &typeQualifierBuilder, + const TSourceLoc &nameLine, + const TString &blockName, + TFieldList *fieldList, + const TString *instanceName, + const TSourceLoc &instanceLine, + TIntermTyped *arrayIndex, + const TSourceLoc &arrayIndexLine) { - if (reservedErrorCheck(nameLine, blockName)) - recover(); + checkIsNotReserved(nameLine, blockName); + + TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics); + + if (mShaderVersion < 310 && typeQualifier.qualifier != EvqUniform) + { + error(typeQualifier.line, + "invalid qualifier: interface blocks must be uniform in version lower than GLSL ES " + "3.10", + getQualifierString(typeQualifier.qualifier)); + } + else if (typeQualifier.qualifier != EvqUniform && typeQualifier.qualifier != EvqBuffer) + { + error(typeQualifier.line, "invalid qualifier: interface blocks must be uniform or buffer", + getQualifierString(typeQualifier.qualifier)); + } - if (typeQualifier.qualifier != EvqUniform) + if (typeQualifier.invariant) { - error(typeQualifier.line, "invalid qualifier:", getQualifierString(typeQualifier.qualifier), - "interface blocks must be uniform"); - recover(); + error(typeQualifier.line, "invalid qualifier on interface block member", "invariant"); } - TLayoutQualifier blockLayoutQualifier = typeQualifier.layoutQualifier; - if (layoutLocationErrorCheck(typeQualifier.line, blockLayoutQualifier)) + if (typeQualifier.qualifier != EvqBuffer) + { + checkMemoryQualifierIsNotSpecified(typeQualifier.memoryQualifier, typeQualifier.line); + } + + // add array index + unsigned int arraySize = 0; + if (arrayIndex != nullptr) { - recover(); + arraySize = checkIsValidArraySize(arrayIndexLine, arrayIndex); } + if (mShaderVersion < 310) + { + checkBindingIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.binding); + } + else + { + checkBlockBindingIsValid(typeQualifier.line, typeQualifier.qualifier, + typeQualifier.layoutQualifier.binding, arraySize); + } + + checkYuvIsNotSpecified(typeQualifier.line, typeQualifier.layoutQualifier.yuv); + + TLayoutQualifier blockLayoutQualifier = typeQualifier.layoutQualifier; + checkLocationIsNotSpecified(typeQualifier.line, blockLayoutQualifier); + checkStd430IsForShaderStorageBlock(typeQualifier.line, blockLayoutQualifier.blockStorage, + typeQualifier.qualifier); + if (blockLayoutQualifier.matrixPacking == EmpUnspecified) { - blockLayoutQualifier.matrixPacking = mDefaultMatrixPacking; + if (typeQualifier.qualifier == EvqUniform) + { + blockLayoutQualifier.matrixPacking = mDefaultUniformMatrixPacking; + } + else if (typeQualifier.qualifier == EvqBuffer) + { + blockLayoutQualifier.matrixPacking = mDefaultBufferMatrixPacking; + } } if (blockLayoutQualifier.blockStorage == EbsUnspecified) { - blockLayoutQualifier.blockStorage = mDefaultBlockStorage; + if (typeQualifier.qualifier == EvqUniform) + { + blockLayoutQualifier.blockStorage = mDefaultUniformBlockStorage; + } + else if (typeQualifier.qualifier == EvqBuffer) + { + blockLayoutQualifier.blockStorage = mDefaultBufferBlockStorage; + } } - TSymbol *blockNameSymbol = new TInterfaceBlockName(&blockName); - if (!symbolTable.declare(blockNameSymbol)) + checkWorkGroupSizeIsNotSpecified(nameLine, blockLayoutQualifier); + + checkInternalFormatIsNotSpecified(nameLine, blockLayoutQualifier.imageInternalFormat); + + if (!symbolTable.declareInterfaceBlockName(&blockName)) { - error(nameLine, "redefinition", blockName.c_str(), "interface block name"); - recover(); + error(nameLine, "redefinition of an interface block name", blockName.c_str()); } // check for sampler types and apply layout qualifiers @@ -2590,38 +3724,53 @@ TIntermAggregate *TParseContext::addInterfaceBlock(const TPublicType &typeQualif { TField *field = (*fieldList)[memberIndex]; TType *fieldType = field->type(); - if (IsSampler(fieldType->getBasicType())) + if (IsOpaqueType(fieldType->getBasicType())) { - error(field->line(), "unsupported type", fieldType->getBasicString(), - "sampler types are not allowed in interface blocks"); - recover(); + std::string reason("unsupported type - "); + reason += fieldType->getBasicString(); + reason += " types are not allowed in interface blocks"; + error(field->line(), reason.c_str(), fieldType->getBasicString()); } const TQualifier qualifier = fieldType->getQualifier(); switch (qualifier) { case EvqGlobal: + break; case EvqUniform: + if (typeQualifier.qualifier == EvqBuffer) + { + error(field->line(), "invalid qualifier on shader storage block member", + getQualifierString(qualifier)); + } + break; + case EvqBuffer: + if (typeQualifier.qualifier == EvqUniform) + { + error(field->line(), "invalid qualifier on uniform block member", + getQualifierString(qualifier)); + } break; default: error(field->line(), "invalid qualifier on interface block member", getQualifierString(qualifier)); - recover(); break; } - // check layout qualifiers - TLayoutQualifier fieldLayoutQualifier = fieldType->getLayoutQualifier(); - if (layoutLocationErrorCheck(field->line(), fieldLayoutQualifier)) + if (fieldType->isInvariant()) { - recover(); + error(field->line(), "invalid qualifier on interface block member", "invariant"); } + // check layout qualifiers + TLayoutQualifier fieldLayoutQualifier = fieldType->getLayoutQualifier(); + checkLocationIsNotSpecified(field->line(), fieldLayoutQualifier); + checkBindingIsNotSpecified(field->line(), fieldLayoutQualifier.binding); + if (fieldLayoutQualifier.blockStorage != EbsUnspecified) { - error(field->line(), "invalid layout qualifier:", - getBlockStorageString(fieldLayoutQualifier.blockStorage), "cannot be used here"); - recover(); + error(field->line(), "invalid layout qualifier: cannot be used here", + getBlockStorageString(fieldLayoutQualifier.blockStorage)); } if (fieldLayoutQualifier.matrixPacking == EmpUnspecified) @@ -2630,29 +3779,51 @@ TIntermAggregate *TParseContext::addInterfaceBlock(const TPublicType &typeQualif } else if (!fieldType->isMatrix() && fieldType->getBasicType() != EbtStruct) { - warning(field->line(), "extraneous layout qualifier:", - getMatrixPackingString(fieldLayoutQualifier.matrixPacking), - "only has an effect on matrix types"); + warning(field->line(), + "extraneous layout qualifier: only has an effect on matrix types", + getMatrixPackingString(fieldLayoutQualifier.matrixPacking)); } fieldType->setLayoutQualifier(fieldLayoutQualifier); - } - // add array index - int arraySize = 0; - if (arrayIndex != NULL) - { - if (arraySizeErrorCheck(arrayIndexLine, arrayIndex, arraySize)) - recover(); + if (mShaderVersion < 310 || memberIndex != fieldList->size() - 1u || + typeQualifier.qualifier != EvqBuffer) + { + // ESSL 3.10 spec section 4.1.9 allows for runtime-sized arrays. + checkIsNotUnsizedArray(field->line(), + "array members of interface blocks must specify a size", + field->name().c_str(), field->type()); + } + + if (typeQualifier.qualifier == EvqBuffer) + { + // set memory qualifiers + // GLSL ES 3.10 session 4.9 [Memory Access Qualifiers]. When a block declaration is + // qualified with a memory qualifier, it is as if all of its members were declared with + // the same memory qualifier. + const TMemoryQualifier &blockMemoryQualifier = typeQualifier.memoryQualifier; + TMemoryQualifier fieldMemoryQualifier = fieldType->getMemoryQualifier(); + fieldMemoryQualifier.readonly |= blockMemoryQualifier.readonly; + fieldMemoryQualifier.writeonly |= blockMemoryQualifier.writeonly; + fieldMemoryQualifier.coherent |= blockMemoryQualifier.coherent; + fieldMemoryQualifier.restrictQualifier |= blockMemoryQualifier.restrictQualifier; + fieldMemoryQualifier.volatileQualifier |= blockMemoryQualifier.volatileQualifier; + // TODO(jiajia.qin@intel.com): Decide whether if readonly and writeonly buffer variable + // is legal. See bug https://github.com/KhronosGroup/OpenGL-API/issues/7 + fieldType->setMemoryQualifier(fieldMemoryQualifier); + } } TInterfaceBlock *interfaceBlock = - new TInterfaceBlock(&blockName, fieldList, instanceName, arraySize, blockLayoutQualifier); - TType interfaceBlockType(interfaceBlock, typeQualifier.qualifier, blockLayoutQualifier, - arraySize); + new TInterfaceBlock(&blockName, fieldList, instanceName, blockLayoutQualifier); + TType interfaceBlockType(interfaceBlock, typeQualifier.qualifier, blockLayoutQualifier); + if (arrayIndex != nullptr) + { + interfaceBlockType.makeArray(arraySize); + } TString symbolName = ""; - int symbolId = 0; + const TSymbolUniqueId *symbolId = nullptr; if (!instanceName) { @@ -2665,60 +3836,64 @@ TIntermAggregate *TParseContext::addInterfaceBlock(const TPublicType &typeQualif // set parent pointer of the field variable fieldType->setInterfaceBlock(interfaceBlock); - TVariable *fieldVariable = new TVariable(&field->name(), *fieldType); - fieldVariable->setQualifier(typeQualifier.qualifier); + TVariable *fieldVariable = symbolTable.declareVariable(&field->name(), *fieldType); - if (!symbolTable.declare(fieldVariable)) + if (fieldVariable) { - error(field->line(), "redefinition", field->name().c_str(), - "interface block member name"); - recover(); + fieldVariable->setQualifier(typeQualifier.qualifier); + } + else + { + error(field->line(), "redefinition of an interface block member name", + field->name().c_str()); } } + symbolId = &symbolTable.getEmptySymbolId(); } else { - if (reservedErrorCheck(instanceLine, *instanceName)) - recover(); + checkIsNotReserved(instanceLine, *instanceName); // add a symbol for this interface block - TVariable *instanceTypeDef = new TVariable(instanceName, interfaceBlockType, false); - instanceTypeDef->setQualifier(typeQualifier.qualifier); - - if (!symbolTable.declare(instanceTypeDef)) + TVariable *instanceTypeDef = symbolTable.declareVariable(instanceName, interfaceBlockType); + if (instanceTypeDef) { - error(instanceLine, "redefinition", instanceName->c_str(), - "interface block instance name"); - recover(); + instanceTypeDef->setQualifier(typeQualifier.qualifier); + symbolId = &instanceTypeDef->getUniqueId(); } - - symbolId = instanceTypeDef->getUniqueId(); - symbolName = instanceTypeDef->getName(); + else + { + error(instanceLine, "redefinition of an interface block instance name", + instanceName->c_str()); + } + symbolName = *instanceName; } - TIntermAggregate *aggregate = intermediate.makeAggregate( - intermediate.addSymbol(symbolId, symbolName, interfaceBlockType, typeQualifier.line), - nameLine); - aggregate->setOp(EOpDeclaration); + TIntermDeclaration *declaration = nullptr; + + if (symbolId) + { + TIntermSymbol *blockSymbol = new TIntermSymbol(*symbolId, symbolName, interfaceBlockType); + blockSymbol->setLine(typeQualifier.line); + declaration = new TIntermDeclaration(); + declaration->appendDeclarator(blockSymbol); + declaration->setLine(nameLine); + } exitStructDeclaration(); - return aggregate; + return declaration; } -bool TParseContext::enterStructDeclaration(const TSourceLoc &line, const TString &identifier) +void TParseContext::enterStructDeclaration(const TSourceLoc &line, const TString &identifier) { ++mStructNestingLevel; // Embedded structure definitions are not supported per GLSL ES spec. - // They aren't allowed in GLSL either, but we need to detect this here - // so we don't rely on the GLSL compiler to catch it. + // ESSL 1.00.17 section 10.9. ESSL 3.00.6 section 12.11. if (mStructNestingLevel > 1) { - error(line, "", "Embedded struct definitions are not allowed"); - return true; + error(line, "Embedded struct definitions are not allowed", "struct"); } - - return false; } void TParseContext::exitStructDeclaration() @@ -2726,22 +3901,16 @@ void TParseContext::exitStructDeclaration() --mStructNestingLevel; } -namespace -{ -const int kWebGLMaxStructNesting = 4; - -} // namespace - -bool TParseContext::structNestingErrorCheck(const TSourceLoc &line, const TField &field) +void TParseContext::checkIsBelowStructNestingLimit(const TSourceLoc &line, const TField &field) { - if (!IsWebGLBasedSpec(mShaderSpec)) + if (!sh::IsWebGLBasedSpec(mShaderSpec)) { - return false; + return; } if (field.type()->getBasicType() != EbtStruct) { - return false; + return; } // We're already inside a structure definition at this point, so add @@ -2752,11 +3921,9 @@ bool TParseContext::structNestingErrorCheck(const TSourceLoc &line, const TField 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; + error(line, reason.c_str(), field.name().c_str()); + return; } - - return false; } // @@ -2766,8 +3933,6 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression, const TSourceLoc &location, TIntermTyped *indexExpression) { - TIntermTyped *indexedExpression = NULL; - if (!baseExpression->isArray() && !baseExpression->isMatrix() && !baseExpression->isVector()) { if (baseExpression->getAsSymbolNode()) @@ -2779,7 +3944,18 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression, { error(location, " left of '[' is not of type array, matrix, or vector ", "expression"); } - recover(); + + return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst)); + } + + if (baseExpression->getQualifier() == EvqPerVertexIn) + { + ASSERT(mShaderType == GL_GEOMETRY_SHADER_OES); + if (mGeometryShaderInputPrimitiveType == EptUndefined) + { + error(location, "missing input primitive declaration before indexing gl_in.", "["); + return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst)); + } } TIntermConstantUnion *indexConstantUnion = indexExpression->getAsConstantUnion(); @@ -2792,174 +3968,139 @@ TIntermTyped *TParseContext::addIndexExpression(TIntermTyped *baseExpression, { if (baseExpression->isInterfaceBlock()) { - error( - location, "", "[", - "array indexes for interface blocks arrays must be constant integral expressions"); - recover(); + // TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks. + switch (baseExpression->getQualifier()) + { + case EvqPerVertexIn: + break; + case EvqUniform: + case EvqBuffer: + error(location, + "array indexes for uniform block arrays and shader storage block arrays " + "must be constant integral expressions", + "["); + break; + default: + // We can reach here only in error cases. + ASSERT(mDiagnostics->numErrors() > 0); + break; + } } else if (baseExpression->getQualifier() == EvqFragmentOut) { - error(location, "", "[", - "array indexes for fragment outputs must be constant integral expressions"); - recover(); + error(location, + "array indexes for fragment outputs must be constant integral expressions", "["); } else if (mShaderSpec == SH_WEBGL2_SPEC && baseExpression->getQualifier() == EvqFragData) { - error(location, "", "[", "array index for gl_FragData must be constant zero"); - recover(); + error(location, "array index for gl_FragData must be constant zero", "["); } } if (indexConstantUnion) { - // If the index is not qualified as constant, the behavior in the spec is undefined. This - // applies even if ANGLE has been able to constant fold it (ANGLE may constant fold - // expressions that are not constant expressions). The most compatible way to handle this - // case is to report a warning instead of an error and force the index to be in the - // correct range. + // If an out-of-range index is not qualified as constant, the behavior in the spec is + // undefined. This applies even if ANGLE has been able to constant fold it (ANGLE may + // constant fold expressions that are not constant expressions). The most compatible way to + // handle this case is to report a warning instead of an error and force the index to be in + // the correct range. bool outOfRangeIndexIsError = indexExpression->getQualifier() == EvqConst; - int index = indexConstantUnion->getIConst(0); - if (index < 0) + int index = 0; + if (indexConstantUnion->getBasicType() == EbtInt) { - std::stringstream infoStream; - infoStream << index; - std::string info = infoStream.str(); - outOfRangeError(outOfRangeIndexIsError, location, "negative index", info.c_str()); - index = 0; + index = indexConstantUnion->getIConst(0); } - TIntermConstantUnion *baseConstantUnion = baseExpression->getAsConstantUnion(); - if (baseConstantUnion) + else if (indexConstantUnion->getBasicType() == EbtUInt) { - if (baseExpression->isArray()) - { - // constant folding for array indexing - indexedExpression = - addConstArrayNode(index, baseConstantUnion, location, outOfRangeIndexIsError); - } - else if (baseExpression->isVector()) - { - // constant folding for vector indexing - 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, baseConstantUnion, location, outOfRangeIndexIsError); - } - else if (baseExpression->isMatrix()) - { - // constant folding for matrix indexing - indexedExpression = - addConstMatrixNode(index, baseConstantUnion, location, outOfRangeIndexIsError); - } + index = static_cast<int>(indexConstantUnion->getUConst(0)); } - else + + int safeIndex = -1; + + if (index < 0) { - int safeIndex = -1; + outOfRangeError(outOfRangeIndexIsError, location, "index expression is negative", "[]"); + safeIndex = 0; + } + if (!baseExpression->getType().isUnsizedArray()) + { if (baseExpression->isArray()) { if (baseExpression->getQualifier() == EvqFragData && index > 0) { - if (mShaderSpec == SH_WEBGL2_SPEC) + if (!isExtensionEnabled(TExtension::EXT_draw_buffers)) { - // Error has been already generated if index is not const. - if (indexExpression->getQualifier() == EvqConst) - { - error(location, "", "[", - "array index for gl_FragData must be constant zero"); - recover(); - } - safeIndex = 0; - } - else if (!isExtensionEnabled("GL_EXT_draw_buffers")) - { - outOfRangeError(outOfRangeIndexIsError, location, "", "[", + outOfRangeError(outOfRangeIndexIsError, location, "array index for gl_FragData must be zero when " - "GL_EXT_draw_buffers is disabled"); + "GL_EXT_draw_buffers is disabled", + "[]"); safeIndex = 0; } } // Only do generic out-of-range check if similar error hasn't already been reported. - if (safeIndex < 0 && index >= baseExpression->getType().getArraySize()) + if (safeIndex < 0) { - std::stringstream extraInfoStream; - extraInfoStream << "array index out of range '" << index << "'"; - std::string extraInfo = extraInfoStream.str(); - outOfRangeError(outOfRangeIndexIsError, location, "", "[", extraInfo.c_str()); - safeIndex = baseExpression->getType().getArraySize() - 1; + safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index, + baseExpression->getOutermostArraySize(), + "array index out of range"); } } - else if ((baseExpression->isVector() || baseExpression->isMatrix()) && - baseExpression->getType().getNominalSize() <= index) + else if (baseExpression->isMatrix()) + { + safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index, + baseExpression->getType().getCols(), + "matrix field selection out of range"); + } + else if (baseExpression->isVector()) { - std::stringstream extraInfoStream; - extraInfoStream << "field selection out of range '" << index << "'"; - std::string extraInfo = extraInfoStream.str(); - outOfRangeError(outOfRangeIndexIsError, location, "", "[", extraInfo.c_str()); - safeIndex = baseExpression->getType().getNominalSize() - 1; + safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index, + baseExpression->getType().getNominalSize(), + "vector field selection out of range"); } + ASSERT(safeIndex >= 0); // Data of constant unions can't be changed, because it may be shared with other // constant unions or even builtins, like gl_MaxDrawBuffers. Instead use a new // sanitized object. - if (safeIndex != -1) + if (safeIndex != index || indexConstantUnion->getBasicType() != EbtInt) { TConstantUnion *safeConstantUnion = new TConstantUnion(); safeConstantUnion->setIConst(safeIndex); indexConstantUnion->replaceConstantUnion(safeConstantUnion); + indexConstantUnion->getTypePointer()->setBasicType(EbtInt); } - indexedExpression = - intermediate.addIndex(EOpIndexDirect, baseExpression, indexExpression, location); + TIntermBinary *node = + new TIntermBinary(EOpIndexDirect, baseExpression, indexExpression); + node->setLine(location); + return node->fold(mDiagnostics); } } - else - { - indexedExpression = - intermediate.addIndex(EOpIndexIndirect, baseExpression, indexExpression, location); - } - if (indexedExpression == 0) - { - TConstantUnion *unionArray = new TConstantUnion[1]; - unionArray->setFConst(0.0f); - indexedExpression = - intermediate.addConstantUnion(unionArray, TType(EbtFloat, EbpHigh, EvqConst), location); - } - else if (baseExpression->isArray()) - { - TType indexedType = baseExpression->getType(); - indexedType.clearArrayness(); - indexedExpression->setType(indexedType); - } - else if (baseExpression->isMatrix()) - { - indexedExpression->setType(TType(baseExpression->getBasicType(), - baseExpression->getPrecision(), EvqTemporary, - static_cast<unsigned char>(baseExpression->getRows()))); - } - else if (baseExpression->isVector()) - { - indexedExpression->setType( - TType(baseExpression->getBasicType(), baseExpression->getPrecision(), EvqTemporary)); - } - else - { - indexedExpression->setType(baseExpression->getType()); - } + TIntermBinary *node = new TIntermBinary(EOpIndexIndirect, baseExpression, indexExpression); + node->setLine(location); + // Indirect indexing can never be constant folded. + return node; +} - if (baseExpression->getType().getQualifier() == EvqConst && - indexExpression->getType().getQualifier() == EvqConst) - { - indexedExpression->getTypePointer()->setQualifier(EvqConst); - } - else +int TParseContext::checkIndexLessThan(bool outOfRangeIndexIsError, + const TSourceLoc &location, + int index, + int arraySize, + const char *reason) +{ + // Should not reach here with an unsized / runtime-sized array. + ASSERT(arraySize > 0); + if (index >= arraySize) { - indexedExpression->getTypePointer()->setQualifier(EvqTemporary); + std::stringstream reasonStream; + reasonStream << reason << " '" << index << "'"; + std::string token = reasonStream.str(); + outOfRangeError(outOfRangeIndexIsError, location, reason, "[]"); + return arraySize - 1; } - - return indexedExpression; + return index; } TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpression, @@ -2967,62 +4108,37 @@ TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpre const TString &fieldString, const TSourceLoc &fieldLocation) { - TIntermTyped *indexedExpression = NULL; - if (baseExpression->isArray()) { error(fieldLocation, "cannot apply dot operator to an array", "."); - recover(); + return baseExpression; } if (baseExpression->isVector()) { - TVectorFields fields; - if (!parseVectorFields(fieldString, baseExpression->getNominalSize(), fields, - fieldLocation)) + TVector<int> fieldOffsets; + if (!parseVectorFields(fieldLocation, fieldString, baseExpression->getNominalSize(), + &fieldOffsets)) { - fields.num = 1; - fields.offsets[0] = 0; - recover(); + fieldOffsets.resize(1); + fieldOffsets[0] = 0; } + TIntermSwizzle *node = new TIntermSwizzle(baseExpression, fieldOffsets); + node->setLine(dotLocation); - if (baseExpression->getAsConstantUnion()) - { - // constant folding for vector fields - indexedExpression = addConstVectorNode(fields, baseExpression->getAsConstantUnion(), - fieldLocation, true); - } - else - { - TIntermTyped *index = intermediate.addSwizzle(fields, fieldLocation); - indexedExpression = - intermediate.addIndex(EOpVectorSwizzle, baseExpression, index, dotLocation); - } - if (indexedExpression == nullptr) - { - recover(); - indexedExpression = baseExpression; - } - else - { - // Note that the qualifier set here will be corrected later. - indexedExpression->setType(TType(baseExpression->getBasicType(), - baseExpression->getPrecision(), EvqTemporary, - (unsigned char)(fieldString).size())); - } + return node->fold(); } 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; + return baseExpression; } else { + bool fieldFound = false; unsigned int i; for (i = 0; i < fields.size(); ++i) { @@ -3034,50 +4150,31 @@ TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpre } if (fieldFound) { - if (baseExpression->getAsConstantUnion()) - { - indexedExpression = addConstStruct(fieldString, baseExpression, dotLocation); - if (indexedExpression == 0) - { - recover(); - indexedExpression = baseExpression; - } - else - { - indexedExpression->setType(*fields[i]->type()); - } - } - else - { - TConstantUnion *unionArray = new TConstantUnion[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()); - } + TIntermTyped *index = CreateIndexNode(i); + index->setLine(fieldLocation); + TIntermBinary *node = + new TIntermBinary(EOpIndexDirectStruct, baseExpression, index); + node->setLine(dotLocation); + return node->fold(mDiagnostics); } else { error(dotLocation, " no such field in structure", fieldString.c_str()); - recover(); - indexedExpression = baseExpression; + return 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; + return baseExpression; } else { + bool fieldFound = false; unsigned int i; for (i = 0; i < fields.size(); ++i) { @@ -3089,19 +4186,18 @@ TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpre } if (fieldFound) { - TConstantUnion *unionArray = new TConstantUnion[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()); + TIntermTyped *index = CreateIndexNode(i); + index->setLine(fieldLocation); + TIntermBinary *node = + new TIntermBinary(EOpIndexDirectInterfaceBlock, baseExpression, index); + node->setLine(dotLocation); + // Indexing interface blocks can never be constant folded. + return node; } else { error(dotLocation, " no such field in interface block", fieldString.c_str()); - recover(); - indexedExpression = baseExpression; + return baseExpression; } } } @@ -3119,39 +4215,36 @@ TIntermTyped *TParseContext::addFieldSelectionExpression(TIntermTyped *baseExpre "side", fieldString.c_str()); } - recover(); - indexedExpression = baseExpression; - } - - if (baseExpression->getQualifier() == EvqConst) - { - indexedExpression->getTypePointer()->setQualifier(EvqConst); - } - else - { - indexedExpression->getTypePointer()->setQualifier(EvqTemporary); + return baseExpression; } - - return indexedExpression; } TLayoutQualifier TParseContext::parseLayoutQualifier(const TString &qualifierType, const TSourceLoc &qualifierTypeLine) { - TLayoutQualifier qualifier; - - qualifier.location = -1; - qualifier.matrixPacking = EmpUnspecified; - qualifier.blockStorage = EbsUnspecified; + TLayoutQualifier qualifier = TLayoutQualifier::Create(); if (qualifierType == "shared") { + if (sh::IsWebGLBasedSpec(mShaderSpec)) + { + error(qualifierTypeLine, "Only std140 layout is allowed in WebGL", "shared"); + } qualifier.blockStorage = EbsShared; } else if (qualifierType == "packed") { + if (sh::IsWebGLBasedSpec(mShaderSpec)) + { + error(qualifierTypeLine, "Only std140 layout is allowed in WebGL", "packed"); + } qualifier.blockStorage = EbsPacked; } + else if (qualifierType == "std430") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.blockStorage = EbsStd430; + } else if (qualifierType == "std140") { qualifier.blockStorage = EbsStd140; @@ -3166,208 +4259,554 @@ TLayoutQualifier TParseContext::parseLayoutQualifier(const TString &qualifierTyp } else if (qualifierType == "location") { - error(qualifierTypeLine, "invalid layout qualifier", qualifierType.c_str(), - "location requires an argument"); - recover(); + error(qualifierTypeLine, "invalid layout qualifier: location requires an argument", + qualifierType.c_str()); + } + else if (qualifierType == "yuv" && mShaderType == GL_FRAGMENT_SHADER) + { + if (checkCanUseExtension(qualifierTypeLine, TExtension::EXT_YUV_target)) + { + qualifier.yuv = true; + } + } + else if (qualifierType == "rgba32f") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA32F; + } + else if (qualifierType == "rgba16f") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA16F; + } + else if (qualifierType == "r32f") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifR32F; + } + else if (qualifierType == "rgba8") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA8; + } + else if (qualifierType == "rgba8_snorm") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA8_SNORM; + } + else if (qualifierType == "rgba32i") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA32I; + } + else if (qualifierType == "rgba16i") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA16I; + } + else if (qualifierType == "rgba8i") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA8I; + } + else if (qualifierType == "r32i") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifR32I; + } + else if (qualifierType == "rgba32ui") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA32UI; + } + else if (qualifierType == "rgba16ui") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA16UI; + } + else if (qualifierType == "rgba8ui") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifRGBA8UI; + } + else if (qualifierType == "r32ui") + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.imageInternalFormat = EiifR32UI; } + else if (qualifierType == "points" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptPoints; + } + else if (qualifierType == "lines" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptLines; + } + else if (qualifierType == "lines_adjacency" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptLinesAdjacency; + } + else if (qualifierType == "triangles" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptTriangles; + } + else if (qualifierType == "triangles_adjacency" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptTrianglesAdjacency; + } + else if (qualifierType == "line_strip" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptLineStrip; + } + else if (qualifierType == "triangle_strip" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + qualifier.primitiveType = EptTriangleStrip; + } + else { error(qualifierTypeLine, "invalid layout qualifier", qualifierType.c_str()); - recover(); } return qualifier; } +void TParseContext::parseLocalSize(const TString &qualifierType, + const TSourceLoc &qualifierTypeLine, + int intValue, + const TSourceLoc &intValueLine, + const std::string &intValueString, + size_t index, + sh::WorkGroupSize *localSize) +{ + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + if (intValue < 1) + { + std::stringstream reasonStream; + reasonStream << "out of range: " << getWorkGroupSizeString(index) << " must be positive"; + std::string reason = reasonStream.str(); + error(intValueLine, reason.c_str(), intValueString.c_str()); + } + (*localSize)[index] = intValue; +} + +void TParseContext::parseNumViews(int intValue, + const TSourceLoc &intValueLine, + const std::string &intValueString, + int *numViews) +{ + // This error is only specified in WebGL, but tightens unspecified behavior in the native + // specification. + if (intValue < 1) + { + error(intValueLine, "out of range: num_views must be positive", intValueString.c_str()); + } + *numViews = intValue; +} + +void TParseContext::parseInvocations(int intValue, + const TSourceLoc &intValueLine, + const std::string &intValueString, + int *numInvocations) +{ + // Although SPEC isn't clear whether invocations can be less than 1, we add this limit because + // it doesn't make sense to accept invocations <= 0. + if (intValue < 1 || intValue > mMaxGeometryShaderInvocations) + { + error(intValueLine, + "out of range: invocations must be in the range of [1, " + "MAX_GEOMETRY_SHADER_INVOCATIONS_OES]", + intValueString.c_str()); + } + else + { + *numInvocations = intValue; + } +} + +void TParseContext::parseMaxVertices(int intValue, + const TSourceLoc &intValueLine, + const std::string &intValueString, + int *maxVertices) +{ + // Although SPEC isn't clear whether max_vertices can be less than 0, we add this limit because + // it doesn't make sense to accept max_vertices < 0. + if (intValue < 0 || intValue > mMaxGeometryShaderMaxVertices) + { + error( + intValueLine, + "out of range: max_vertices must be in the range of [0, gl_MaxGeometryOutputVertices]", + intValueString.c_str()); + } + else + { + *maxVertices = intValue; + } +} + TLayoutQualifier TParseContext::parseLayoutQualifier(const TString &qualifierType, const TSourceLoc &qualifierTypeLine, - const TString &intValueString, int intValue, const TSourceLoc &intValueLine) { - TLayoutQualifier qualifier; + TLayoutQualifier qualifier = TLayoutQualifier::Create(); - qualifier.location = -1; - qualifier.matrixPacking = EmpUnspecified; - qualifier.blockStorage = EbsUnspecified; + std::string intValueString = Str(intValue); - if (qualifierType != "location") + if (qualifierType == "location") + { + // must check that location is non-negative + if (intValue < 0) + { + error(intValueLine, "out of range: location must be non-negative", + intValueString.c_str()); + } + else + { + qualifier.location = intValue; + qualifier.locationsSpecified = 1; + } + } + else if (qualifierType == "binding") { - error(qualifierTypeLine, "invalid layout qualifier", qualifierType.c_str(), - "only location may have arguments"); - recover(); + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); + if (intValue < 0) + { + error(intValueLine, "out of range: binding must be non-negative", + intValueString.c_str()); + } + else + { + qualifier.binding = intValue; + } } - else + else if (qualifierType == "offset") { - // must check that location is non-negative + checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); if (intValue < 0) { - error(intValueLine, "out of range:", intValueString.c_str(), - "location must be non-negative"); - recover(); + error(intValueLine, "out of range: offset must be non-negative", + intValueString.c_str()); } else { - qualifier.location = intValue; + qualifier.offset = intValue; + } + } + else if (qualifierType == "local_size_x") + { + parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 0u, + &qualifier.localSize); + } + else if (qualifierType == "local_size_y") + { + parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 1u, + &qualifier.localSize); + } + else if (qualifierType == "local_size_z") + { + parseLocalSize(qualifierType, qualifierTypeLine, intValue, intValueLine, intValueString, 2u, + &qualifier.localSize); + } + else if (qualifierType == "num_views" && mShaderType == GL_VERTEX_SHADER) + { + if (checkCanUseExtension(qualifierTypeLine, TExtension::OVR_multiview)) + { + parseNumViews(intValue, intValueLine, intValueString, &qualifier.numViews); } } + else if (qualifierType == "invocations" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + parseInvocations(intValue, intValueLine, intValueString, &qualifier.invocations); + } + else if (qualifierType == "max_vertices" && mShaderType == GL_GEOMETRY_SHADER_OES && + checkCanUseExtension(qualifierTypeLine, TExtension::OES_geometry_shader)) + { + parseMaxVertices(intValue, intValueLine, intValueString, &qualifier.maxVertices); + } + + else + { + error(qualifierTypeLine, "invalid layout qualifier", qualifierType.c_str()); + } return qualifier; } -TLayoutQualifier TParseContext::joinLayoutQualifiers(TLayoutQualifier leftQualifier, - TLayoutQualifier rightQualifier) +TTypeQualifierBuilder *TParseContext::createTypeQualifierBuilder(const TSourceLoc &loc) { - TLayoutQualifier joinedQualifier = leftQualifier; + return new TTypeQualifierBuilder( + new TStorageQualifierWrapper(symbolTable.atGlobalLevel() ? EvqGlobal : EvqTemporary, loc), + mShaderVersion); +} + +TStorageQualifierWrapper *TParseContext::parseGlobalStorageQualifier(TQualifier qualifier, + const TSourceLoc &loc) +{ + checkIsAtGlobalLevel(loc, getQualifierString(qualifier)); + return new TStorageQualifierWrapper(qualifier, loc); +} - if (rightQualifier.location != -1) +TStorageQualifierWrapper *TParseContext::parseVaryingQualifier(const TSourceLoc &loc) +{ + if (getShaderType() == GL_VERTEX_SHADER) { - joinedQualifier.location = rightQualifier.location; + return parseGlobalStorageQualifier(EvqVaryingOut, loc); } - if (rightQualifier.matrixPacking != EmpUnspecified) + return parseGlobalStorageQualifier(EvqVaryingIn, loc); +} + +TStorageQualifierWrapper *TParseContext::parseInQualifier(const TSourceLoc &loc) +{ + if (declaringFunction()) { - joinedQualifier.matrixPacking = rightQualifier.matrixPacking; + return new TStorageQualifierWrapper(EvqIn, loc); } - if (rightQualifier.blockStorage != EbsUnspecified) + + switch (getShaderType()) { - joinedQualifier.blockStorage = rightQualifier.blockStorage; + case GL_VERTEX_SHADER: + { + if (mShaderVersion < 300 && !isExtensionEnabled(TExtension::OVR_multiview)) + { + error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "in"); + } + return new TStorageQualifierWrapper(EvqVertexIn, loc); + } + case GL_FRAGMENT_SHADER: + { + if (mShaderVersion < 300) + { + error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "in"); + } + return new TStorageQualifierWrapper(EvqFragmentIn, loc); + } + case GL_COMPUTE_SHADER: + { + return new TStorageQualifierWrapper(EvqComputeIn, loc); + } + case GL_GEOMETRY_SHADER_OES: + { + return new TStorageQualifierWrapper(EvqGeometryIn, loc); + } + default: + { + UNREACHABLE(); + return new TStorageQualifierWrapper(EvqLast, loc); + } } - - return joinedQualifier; } -TPublicType TParseContext::joinInterpolationQualifiers(const TSourceLoc &interpolationLoc, - TQualifier interpolationQualifier, - const TSourceLoc &storageLoc, - TQualifier storageQualifier) +TStorageQualifierWrapper *TParseContext::parseOutQualifier(const TSourceLoc &loc) { - TQualifier mergedQualifier = EvqSmoothIn; - - if (storageQualifier == EvqFragmentIn) + if (declaringFunction()) { - if (interpolationQualifier == EvqSmooth) - mergedQualifier = EvqSmoothIn; - else if (interpolationQualifier == EvqFlat) - mergedQualifier = EvqFlatIn; - else - UNREACHABLE(); + return new TStorageQualifierWrapper(EvqOut, loc); } - else if (storageQualifier == EvqCentroidIn) + switch (getShaderType()) { - if (interpolationQualifier == EvqSmooth) - mergedQualifier = EvqCentroidIn; - else if (interpolationQualifier == EvqFlat) - mergedQualifier = EvqFlatIn; - else + case GL_VERTEX_SHADER: + { + if (mShaderVersion < 300) + { + error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "out"); + } + return new TStorageQualifierWrapper(EvqVertexOut, loc); + } + case GL_FRAGMENT_SHADER: + { + if (mShaderVersion < 300) + { + error(loc, "storage qualifier supported in GLSL ES 3.00 and above only", "out"); + } + return new TStorageQualifierWrapper(EvqFragmentOut, loc); + } + case GL_COMPUTE_SHADER: + { + error(loc, "storage qualifier isn't supported in compute shaders", "out"); + return new TStorageQualifierWrapper(EvqLast, loc); + } + case GL_GEOMETRY_SHADER_OES: + { + return new TStorageQualifierWrapper(EvqGeometryOut, loc); + } + default: + { UNREACHABLE(); + return new TStorageQualifierWrapper(EvqLast, loc); + } } - else if (storageQualifier == EvqVertexOut) +} + +TStorageQualifierWrapper *TParseContext::parseInOutQualifier(const TSourceLoc &loc) +{ + if (!declaringFunction()) { - if (interpolationQualifier == EvqSmooth) - mergedQualifier = EvqSmoothOut; - else if (interpolationQualifier == EvqFlat) - mergedQualifier = EvqFlatOut; - else - UNREACHABLE(); + error(loc, "invalid qualifier: can be only used with function parameters", "inout"); } - else if (storageQualifier == EvqCentroidOut) + return new TStorageQualifierWrapper(EvqInOut, loc); +} + +TLayoutQualifier TParseContext::joinLayoutQualifiers(TLayoutQualifier leftQualifier, + TLayoutQualifier rightQualifier, + const TSourceLoc &rightQualifierLocation) +{ + return sh::JoinLayoutQualifiers(leftQualifier, rightQualifier, rightQualifierLocation, + mDiagnostics); +} + +TField *TParseContext::parseStructDeclarator(TString *identifier, const TSourceLoc &loc) +{ + checkIsNotReserved(loc, *identifier); + TType *type = new TType(EbtVoid, EbpUndefined); + return new TField(type, identifier, loc); +} + +TField *TParseContext::parseStructArrayDeclarator(TString *identifier, + const TSourceLoc &loc, + const TVector<unsigned int> &arraySizes, + const TSourceLoc &arraySizeLoc) +{ + checkIsNotReserved(loc, *identifier); + + TType *type = new TType(EbtVoid, EbpUndefined); + type->makeArrays(arraySizes); + + return new TField(type, identifier, loc); +} + +void TParseContext::checkDoesNotHaveDuplicateFieldName(const TFieldList::const_iterator begin, + const TFieldList::const_iterator end, + const TString &name, + const TSourceLoc &location) +{ + for (auto fieldIter = begin; fieldIter != end; ++fieldIter) { - if (interpolationQualifier == EvqSmooth) - mergedQualifier = EvqCentroidOut; - else if (interpolationQualifier == EvqFlat) - mergedQualifier = EvqFlatOut; - else - UNREACHABLE(); + if ((*fieldIter)->name() == name) + { + error(location, "duplicate field name in structure", name.c_str()); + } } - else - { - error(interpolationLoc, - "interpolation qualifier requires a fragment 'in' or vertex 'out' storage qualifier", - getInterpolationString(interpolationQualifier)); - recover(); +} - mergedQualifier = storageQualifier; +TFieldList *TParseContext::addStructFieldList(TFieldList *fields, const TSourceLoc &location) +{ + for (TFieldList::const_iterator fieldIter = fields->begin(); fieldIter != fields->end(); + ++fieldIter) + { + checkDoesNotHaveDuplicateFieldName(fields->begin(), fieldIter, (*fieldIter)->name(), + location); } - - TPublicType type; - type.setBasic(EbtVoid, mergedQualifier, storageLoc); - return type; + return fields; } -TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecifier, - TFieldList *fieldList) +TFieldList *TParseContext::combineStructFieldLists(TFieldList *processedFields, + const TFieldList *newlyAddedFields, + const TSourceLoc &location) { - if (voidErrorCheck(typeSpecifier.line, (*fieldList)[0]->name(), typeSpecifier.type)) + for (TField *field : *newlyAddedFields) { - recover(); + checkDoesNotHaveDuplicateFieldName(processedFields->begin(), processedFields->end(), + field->name(), location); + processedFields->push_back(field); } + return processedFields; +} + +TFieldList *TParseContext::addStructDeclaratorListWithQualifiers( + const TTypeQualifierBuilder &typeQualifierBuilder, + TPublicType *typeSpecifier, + TFieldList *fieldList) +{ + TTypeQualifier typeQualifier = typeQualifierBuilder.getVariableTypeQualifier(mDiagnostics); - for (unsigned int i = 0; i < fieldList->size(); ++i) + typeSpecifier->qualifier = typeQualifier.qualifier; + typeSpecifier->layoutQualifier = typeQualifier.layoutQualifier; + typeSpecifier->memoryQualifier = typeQualifier.memoryQualifier; + typeSpecifier->invariant = typeQualifier.invariant; + if (typeQualifier.precision != EbpUndefined) { - // - // Careful not to replace already known aspects of type, like array-ness - // - TType *type = (*fieldList)[i]->type(); - type->setBasicType(typeSpecifier.type); - type->setPrimarySize(typeSpecifier.primarySize); - type->setSecondarySize(typeSpecifier.secondarySize); - type->setPrecision(typeSpecifier.precision); - type->setQualifier(typeSpecifier.qualifier); - type->setLayoutQualifier(typeSpecifier.layoutQualifier); + typeSpecifier->precision = typeQualifier.precision; + } + return addStructDeclaratorList(*typeSpecifier, fieldList); +} - // 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) +TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecifier, + TFieldList *declaratorList) +{ + checkPrecisionSpecified(typeSpecifier.getLine(), typeSpecifier.precision, + typeSpecifier.getBasicType()); + + checkIsNonVoid(typeSpecifier.getLine(), (*declaratorList)[0]->name(), + typeSpecifier.getBasicType()); + + checkWorkGroupSizeIsNotSpecified(typeSpecifier.getLine(), typeSpecifier.layoutQualifier); + + for (TField *declarator : *declaratorList) + { + // Don't allow arrays of arrays in ESSL < 3.10. + if (declarator->type()->isArray()) { - type->setStruct(typeSpecifier.userDef->getStruct()); + checkArrayElementIsNotArray(typeSpecifier.getLine(), typeSpecifier); } - if (structNestingErrorCheck(typeSpecifier.line, *(*fieldList)[i])) + auto *declaratorArraySizes = declarator->type()->getArraySizes(); + + TType *type = declarator->type(); + *type = TType(typeSpecifier); + if (declaratorArraySizes != nullptr) { - recover(); + for (unsigned int arraySize : *declaratorArraySizes) + { + type->makeArray(arraySize); + } } + + checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *declarator); } - return fieldList; + return declaratorList; } -TPublicType TParseContext::addStructure(const TSourceLoc &structLine, - const TSourceLoc &nameLine, - const TString *structName, - TFieldList *fieldList) +TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine, + const TSourceLoc &nameLine, + const TString *structName, + TFieldList *fieldList) { - TStructure *structure = new TStructure(structName, fieldList); - TType *structureType = new TType(structure); + TStructure *structure = new TStructure(&symbolTable, structName, fieldList); // 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(TSymbolTable::nextUniqueId()); structure->setAtGlobalScope(symbolTable.atGlobalLevel()); if (!structName->empty()) { - if (reservedErrorCheck(nameLine, *structName)) - { - recover(); - } - TVariable *userTypeDef = new TVariable(structName, *structureType, true); - if (!symbolTable.declare(userTypeDef)) + checkIsNotReserved(nameLine, *structName); + if (!symbolTable.declareStructType(structure)) { - error(nameLine, "redefinition", structName->c_str(), "struct"); - recover(); + error(nameLine, "redefinition of a struct", structName->c_str()); } } // 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]; + TField &field = *(*fieldList)[typeListIndex]; const TQualifier qualifier = field.type()->getQualifier(); switch (qualifier) { @@ -3377,22 +4816,37 @@ TPublicType TParseContext::addStructure(const TSourceLoc &structLine, default: error(field.line(), "invalid qualifier on struct member", getQualifierString(qualifier)); - recover(); break; } + if (field.type()->isInvariant()) + { + error(field.line(), "invalid qualifier on struct member", "invariant"); + } + // ESSL 3.10 section 4.1.8 -- atomic_uint or images are not allowed as structure member. + if (IsImage(field.type()->getBasicType()) || IsAtomicCounter(field.type()->getBasicType())) + { + error(field.line(), "disallowed type in struct", field.type()->getBasicString()); + } + + checkIsNotUnsizedArray(field.line(), "array members of structs must specify a size", + field.name().c_str(), field.type()); + + checkMemoryQualifierIsNotSpecified(field.type()->getMemoryQualifier(), field.line()); + + checkBindingIsNotSpecified(field.line(), field.type()->getLayoutQualifier().binding); + + checkLocationIsNotSpecified(field.line(), field.type()->getLayoutQualifier()); } - TPublicType publicType; - publicType.setBasic(EbtStruct, EvqTemporary, structLine); - publicType.userDef = structureType; - publicType.isStructSpecifier = true; + TTypeSpecifierNonArray typeSpecifierNonArray; + typeSpecifierNonArray.initializeStruct(structure, true, structLine); exitStructDeclaration(); - return publicType; + return typeSpecifierNonArray; } TIntermSwitch *TParseContext::addSwitch(TIntermTyped *init, - TIntermAggregate *statementList, + TIntermBlock *statementList, const TSourceLoc &loc) { TBasicType switchType = init->getBasicType(); @@ -3401,26 +4855,18 @@ TIntermSwitch *TParseContext::addSwitch(TIntermTyped *init, { error(init->getLine(), "init-expression in a switch statement must be a scalar integer", "switch"); - recover(); return nullptr; } - if (statementList) + ASSERT(statementList); + if (!ValidateSwitchStatementList(switchType, mShaderVersion, mDiagnostics, statementList, loc)) { - if (!ValidateSwitch::validate(switchType, this, statementList, loc)) - { - recover(); - return nullptr; - } - } - - TIntermSwitch *node = intermediate.addSwitch(init, statementList, loc); - if (node == nullptr) - { - error(loc, "erroneous switch statement", "switch"); - recover(); + ASSERT(mDiagnostics->numErrors() > 0); return nullptr; } + + TIntermSwitch *node = new TIntermSwitch(init, statementList); + node->setLine(loc); return node; } @@ -3429,20 +4875,17 @@ TIntermCase *TParseContext::addCase(TIntermTyped *condition, const TSourceLoc &l if (mSwitchNestingLevel == 0) { error(loc, "case labels need to be inside switch statements", "case"); - recover(); return nullptr; } if (condition == nullptr) { error(loc, "case label must have a condition", "case"); - recover(); return nullptr; } if ((condition->getBasicType() != EbtInt && condition->getBasicType() != EbtUInt) || condition->isMatrix() || condition->isArray() || condition->isVector()) { error(condition->getLine(), "case label must be a scalar integer", "case"); - recover(); } TIntermConstantUnion *conditionConst = condition->getAsConstantUnion(); // TODO(oetuaho@nvidia.com): Get rid of the conditionConst == nullptr check once all constant @@ -3451,15 +4894,9 @@ TIntermCase *TParseContext::addCase(TIntermTyped *condition, const TSourceLoc &l if (condition->getQualifier() != EvqConst || conditionConst == nullptr) { error(condition->getLine(), "case label must be constant", "case"); - recover(); - } - TIntermCase *node = intermediate.addCase(condition, loc); - if (node == nullptr) - { - error(loc, "erroneous case statement", "case"); - recover(); - return nullptr; } + TIntermCase *node = new TIntermCase(condition); + node->setLine(loc); return node; } @@ -3468,28 +4905,18 @@ TIntermCase *TParseContext::addDefault(const TSourceLoc &loc) if (mSwitchNestingLevel == 0) { error(loc, "default labels need to be inside switch statements", "default"); - recover(); - return nullptr; - } - TIntermCase *node = intermediate.addCase(nullptr, loc); - if (node == nullptr) - { - error(loc, "erroneous default statement", "default"); - recover(); return nullptr; } + TIntermCase *node = new TIntermCase(nullptr); + node->setLine(loc); return node; } TIntermTyped *TParseContext::createUnaryMath(TOperator op, TIntermTyped *child, - const TSourceLoc &loc, - const TType *funcReturnType) + const TSourceLoc &loc) { - if (child == nullptr) - { - return nullptr; - } + ASSERT(child != nullptr); switch (op) { @@ -3497,6 +4924,7 @@ TIntermTyped *TParseContext::createUnaryMath(TOperator op, if (child->getBasicType() != EbtBool || child->isMatrix() || child->isArray() || child->isVector()) { + unaryOpError(loc, GetOperatorString(op), child->getCompleteString()); return nullptr; } break; @@ -3504,6 +4932,7 @@ TIntermTyped *TParseContext::createUnaryMath(TOperator op, if ((child->getBasicType() != EbtInt && child->getBasicType() != EbtUInt) || child->isMatrix() || child->isArray()) { + unaryOpError(loc, GetOperatorString(op), child->getCompleteString()); return nullptr; } break; @@ -3513,9 +4942,11 @@ TIntermTyped *TParseContext::createUnaryMath(TOperator op, case EOpPreDecrement: case EOpNegative: case EOpPositive: - if (child->getBasicType() == EbtStruct || child->getBasicType() == EbtBool || - child->isArray()) + if (child->getBasicType() == EbtStruct || child->isInterfaceBlock() || + child->getBasicType() == EbtBool || child->isArray() || + IsOpaqueType(child->getBasicType())) { + unaryOpError(loc, GetOperatorString(op), child->getCompleteString()); return nullptr; } // Operators for built-ins are already type checked against their prototype. @@ -3523,16 +4954,24 @@ TIntermTyped *TParseContext::createUnaryMath(TOperator op, break; } - return intermediate.addUnaryMath(op, child, loc, funcReturnType); + if (child->getMemoryQualifier().writeonly) + { + unaryOpError(loc, GetOperatorString(op), child->getCompleteString()); + return nullptr; + } + + TIntermUnary *node = new TIntermUnary(op, child); + node->setLine(loc); + + return node->fold(mDiagnostics); } TIntermTyped *TParseContext::addUnaryMath(TOperator op, TIntermTyped *child, const TSourceLoc &loc) { - TIntermTyped *node = createUnaryMath(op, child, loc, nullptr); + ASSERT(op != EOpNull); + TIntermTyped *node = createUnaryMath(op, child, loc); if (node == nullptr) { - unaryOpError(loc, GetOperatorString(op), child->getCompleteString()); - recover(); return child; } return node; @@ -3542,8 +4981,7 @@ TIntermTyped *TParseContext::addUnaryMathLValue(TOperator op, TIntermTyped *child, const TSourceLoc &loc) { - if (lValueErrorCheck(loc, GetOperatorString(op), child)) - recover(); + checkCanBeLValue(loc, GetOperatorString(op), child); return addUnaryMath(op, child, loc); } @@ -3552,17 +4990,95 @@ bool TParseContext::binaryOpCommonCheck(TOperator op, TIntermTyped *right, const TSourceLoc &loc) { - if (left->isArray() || right->isArray()) + // Check opaque types are not allowed to be operands in expressions other than array indexing + // and structure member selection. + if (IsOpaqueType(left->getBasicType()) || IsOpaqueType(right->getBasicType())) { - if (mShaderVersion < 300) + switch (op) { - error(loc, "Invalid operation for arrays", GetOperatorString(op)); - return false; + case EOpIndexDirect: + case EOpIndexIndirect: + break; + case EOpIndexDirectStruct: + UNREACHABLE(); + + default: + error(loc, "Invalid operation for variables with an opaque type", + GetOperatorString(op)); + return false; } + } + + if (right->getMemoryQualifier().writeonly) + { + error(loc, "Invalid operation for variables with writeonly", GetOperatorString(op)); + return false; + } - if (left->isArray() != right->isArray()) + if (left->getMemoryQualifier().writeonly) + { + switch (op) { - error(loc, "array / non-array mismatch", GetOperatorString(op)); + case EOpAssign: + case EOpInitialize: + case EOpIndexDirect: + case EOpIndexIndirect: + case EOpIndexDirectStruct: + case EOpIndexDirectInterfaceBlock: + break; + default: + error(loc, "Invalid operation for variables with writeonly", GetOperatorString(op)); + return false; + } + } + + if (left->getType().getStruct() || right->getType().getStruct()) + { + switch (op) + { + case EOpIndexDirectStruct: + ASSERT(left->getType().getStruct()); + break; + case EOpEqual: + case EOpNotEqual: + case EOpAssign: + case EOpInitialize: + if (left->getType() != right->getType()) + { + return false; + } + break; + default: + error(loc, "Invalid operation for structs", GetOperatorString(op)); + return false; + } + } + + if (left->isInterfaceBlock() || right->isInterfaceBlock()) + { + switch (op) + { + case EOpIndexDirectInterfaceBlock: + ASSERT(left->getType().getInterfaceBlock()); + break; + default: + error(loc, "Invalid operation for interface blocks", GetOperatorString(op)); + return false; + } + } + + if (left->isArray() != right->isArray()) + { + error(loc, "array / non-array mismatch", GetOperatorString(op)); + return false; + } + + if (left->isArray()) + { + ASSERT(right->isArray()); + if (mShaderVersion < 300) + { + error(loc, "Invalid operation for arrays", GetOperatorString(op)); return false; } @@ -3578,7 +5094,7 @@ bool TParseContext::binaryOpCommonCheck(TOperator op, return false; } // At this point, size of implicitly sized arrays should be resolved. - if (left->getArraySize() != right->getArraySize()) + if (*left->getType().getArraySizes() != *right->getType().getArraySizes()) { error(loc, "array size mismatch", GetOperatorString(op)); return false; @@ -3625,8 +5141,10 @@ bool TParseContext::binaryOpCommonCheck(TOperator op, return false; } - // Check that type sizes match exactly on ops that require that. - // Also check restrictions for structs that contain arrays or samplers. + // Check that: + // 1. Type sizes match exactly on ops that require that. + // 2. Restrictions for structs that contain arrays or samplers are respected. + // 3. Arithmetic op type dimensionality restrictions for ops other than multiply are respected. switch (op) { case EOpAssign: @@ -3650,15 +5168,65 @@ bool TParseContext::binaryOpCommonCheck(TOperator op, GetOperatorString(op)); return false; } + + if ((left->getNominalSize() != right->getNominalSize()) || + (left->getSecondarySize() != right->getSecondarySize())) + { + error(loc, "dimension mismatch", GetOperatorString(op)); + return false; + } + break; case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: - if ((left->getNominalSize() != right->getNominalSize()) || - (left->getSecondarySize() != right->getSecondarySize())) + if (!left->isScalar() || !right->isScalar()) + { + error(loc, "comparison operator only defined for scalars", GetOperatorString(op)); + return false; + } + break; + case EOpAdd: + case EOpSub: + case EOpDiv: + case EOpIMod: + case EOpBitShiftLeft: + case EOpBitShiftRight: + case EOpBitwiseAnd: + case EOpBitwiseXor: + case EOpBitwiseOr: + case EOpAddAssign: + case EOpSubAssign: + case EOpDivAssign: + case EOpIModAssign: + case EOpBitShiftLeftAssign: + case EOpBitShiftRightAssign: + case EOpBitwiseAndAssign: + case EOpBitwiseXorAssign: + case EOpBitwiseOrAssign: + if ((left->isMatrix() && right->isVector()) || (left->isVector() && right->isMatrix())) { return false; } + + // Are the sizes compatible? + if (left->getNominalSize() != right->getNominalSize() || + left->getSecondarySize() != right->getSecondarySize()) + { + // If the nominal sizes of operands do not match: + // One of them must be a scalar. + if (!left->isScalar() && !right->isScalar()) + return false; + + // In the case of compound assignment other than multiply-assign, + // the right side needs to be a scalar. Otherwise a vector/matrix + // would be assigned to a scalar. A scalar can't be shifted by a + // vector either. + if (!right->isScalar() && + (IsAssignment(op) || op == EOpBitShiftLeft || op == EOpBitShiftRight)) + return false; + } + break; default: break; } @@ -3666,6 +5234,49 @@ bool TParseContext::binaryOpCommonCheck(TOperator op, return true; } +bool TParseContext::isMultiplicationTypeCombinationValid(TOperator op, + const TType &left, + const TType &right) +{ + switch (op) + { + case EOpMul: + case EOpMulAssign: + return left.getNominalSize() == right.getNominalSize() && + left.getSecondarySize() == right.getSecondarySize(); + case EOpVectorTimesScalar: + return true; + case EOpVectorTimesScalarAssign: + ASSERT(!left.isMatrix() && !right.isMatrix()); + return left.isVector() && !right.isVector(); + case EOpVectorTimesMatrix: + return left.getNominalSize() == right.getRows(); + case EOpVectorTimesMatrixAssign: + ASSERT(!left.isMatrix() && right.isMatrix()); + return left.isVector() && left.getNominalSize() == right.getRows() && + left.getNominalSize() == right.getCols(); + case EOpMatrixTimesVector: + return left.getCols() == right.getNominalSize(); + case EOpMatrixTimesScalar: + return true; + case EOpMatrixTimesScalarAssign: + ASSERT(left.isMatrix() && !right.isMatrix()); + return !right.isVector(); + case EOpMatrixTimesMatrix: + return left.getCols() == right.getRows(); + case EOpMatrixTimesMatrixAssign: + ASSERT(left.isMatrix() && right.isMatrix()); + // We need to check two things: + // 1. The matrix multiplication step is valid. + // 2. The result will have the same number of columns as the lvalue. + return left.getCols() == right.getRows() && left.getCols() == right.getCols(); + + default: + UNREACHABLE(); + return false; + } +} + TIntermTyped *TParseContext::addBinaryMathInternal(TOperator op, TIntermTyped *left, TIntermTyped *right, @@ -3678,52 +5289,61 @@ TIntermTyped *TParseContext::addBinaryMathInternal(TOperator op, { case EOpEqual: case EOpNotEqual: - break; case EOpLessThan: case EOpGreaterThan: case EOpLessThanEqual: case EOpGreaterThanEqual: - ASSERT(!left->isArray() && !right->isArray()); - if (left->isMatrix() || left->isVector() || left->getBasicType() == EbtStruct) - { - return nullptr; - } break; case EOpLogicalOr: case EOpLogicalXor: case EOpLogicalAnd: - ASSERT(!left->isArray() && !right->isArray()); - if (left->getBasicType() != EbtBool || left->isMatrix() || left->isVector()) + ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() && + !right->getType().getStruct()); + if (left->getBasicType() != EbtBool || !left->isScalar() || !right->isScalar()) { return nullptr; } + // Basic types matching should have been already checked. + ASSERT(right->getBasicType() == EbtBool); break; case EOpAdd: case EOpSub: case EOpDiv: case EOpMul: - ASSERT(!left->isArray() && !right->isArray()); - if (left->getBasicType() == EbtStruct || left->getBasicType() == EbtBool) + ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() && + !right->getType().getStruct()); + if (left->getBasicType() == EbtBool) { return nullptr; } break; case EOpIMod: - ASSERT(!left->isArray() && !right->isArray()); + ASSERT(!left->isArray() && !right->isArray() && !left->getType().getStruct() && + !right->getType().getStruct()); // Note that this is only for the % operator, not for mod() - if (left->getBasicType() == EbtStruct || left->getBasicType() == EbtBool || - left->getBasicType() == EbtFloat) + if (left->getBasicType() == EbtBool || left->getBasicType() == EbtFloat) { return nullptr; } break; - // Note that for bitwise ops, type checking is done in promote() to - // share code between ops and compound assignment default: break; } - return intermediate.addBinaryMath(op, left, right, loc); + if (op == EOpMul) + { + op = TIntermBinary::GetMulOpBasedOnOperands(left->getType(), right->getType()); + if (!isMultiplicationTypeCombinationValid(op, left->getType(), right->getType())) + { + return nullptr; + } + } + + TIntermBinary *node = new TIntermBinary(op, left, right); + node->setLine(loc); + + // See if we can fold constants. + return node->fold(mDiagnostics); } TIntermTyped *TParseContext::addBinaryMath(TOperator op, @@ -3736,7 +5356,6 @@ TIntermTyped *TParseContext::addBinaryMath(TOperator op, { binaryOpError(loc, GetOperatorString(op), left->getCompleteString(), right->getCompleteString()); - recover(); return left; } return node; @@ -3748,27 +5367,35 @@ TIntermTyped *TParseContext::addBinaryMathBooleanResult(TOperator op, const TSourceLoc &loc) { TIntermTyped *node = addBinaryMathInternal(op, left, right, loc); - if (node == 0) + if (node == nullptr) { binaryOpError(loc, GetOperatorString(op), left->getCompleteString(), right->getCompleteString()); - recover(); - TConstantUnion *unionArray = new TConstantUnion[1]; - unionArray->setBConst(false); - return intermediate.addConstantUnion(unionArray, TType(EbtBool, EbpUndefined, EvqConst), - loc); + node = CreateBoolNode(false); + node->setLine(loc); } return node; } -TIntermTyped *TParseContext::createAssign(TOperator op, - TIntermTyped *left, - TIntermTyped *right, - const TSourceLoc &loc) +TIntermBinary *TParseContext::createAssign(TOperator op, + TIntermTyped *left, + TIntermTyped *right, + const TSourceLoc &loc) { if (binaryOpCommonCheck(op, left, right, loc)) { - return intermediate.addAssign(op, left, right, loc); + if (op == EOpMulAssign) + { + op = TIntermBinary::GetMulAssignOpBasedOnOperands(left->getType(), right->getType()); + if (!isMultiplicationTypeCombinationValid(op, left->getType(), right->getType())) + { + return nullptr; + } + } + TIntermBinary *node = new TIntermBinary(op, left, right); + node->setLine(loc); + + return node; } return nullptr; } @@ -3778,11 +5405,11 @@ TIntermTyped *TParseContext::addAssign(TOperator op, TIntermTyped *right, const TSourceLoc &loc) { + checkCanBeLValue(loc, "assign", left); TIntermTyped *node = createAssign(op, left, right, loc); if (node == nullptr) { assignError(loc, "assign", left->getCompleteString(), right->getCompleteString()); - recover(); return left; } return node; @@ -3792,7 +5419,22 @@ TIntermTyped *TParseContext::addComma(TIntermTyped *left, TIntermTyped *right, const TSourceLoc &loc) { - return intermediate.addComma(left, right, loc, mShaderVersion); + // WebGL2 section 5.26, the following results in an error: + // "Sequence operator applied to void, arrays, or structs containing arrays" + if (mShaderSpec == SH_WEBGL2_SPEC && + (left->isArray() || left->getBasicType() == EbtVoid || + left->getType().isStructureContainingArrays() || right->isArray() || + right->getBasicType() == EbtVoid || right->getType().isStructureContainingArrays())) + { + error(loc, + "sequence operator is not allowed for void, arrays, or structs containing arrays", + ","); + } + + TIntermBinary *commaNode = new TIntermBinary(EOpComma, left, right); + TQualifier resultQualifier = TIntermBinary::GetCommaQualifier(mShaderVersion, left, right); + commaNode->getTypePointer()->setQualifier(resultQualifier); + return commaNode->fold(mDiagnostics); } TIntermBranch *TParseContext::addBranch(TOperator op, const TSourceLoc &loc) @@ -3803,319 +5445,553 @@ TIntermBranch *TParseContext::addBranch(TOperator op, const TSourceLoc &loc) if (mLoopNestingLevel <= 0) { error(loc, "continue statement only allowed in loops", ""); - recover(); } break; case EOpBreak: if (mLoopNestingLevel <= 0 && mSwitchNestingLevel <= 0) { error(loc, "break statement only allowed in loops and switch statements", ""); - recover(); } break; case EOpReturn: if (mCurrentFunctionType->getBasicType() != EbtVoid) { error(loc, "non-void function must return a value", "return"); - recover(); + } + break; + case EOpKill: + if (mShaderType != GL_FRAGMENT_SHADER) + { + error(loc, "discard supported in fragment shaders only", "discard"); } break; default: - // No checks for discard + UNREACHABLE(); break; } - return intermediate.addBranch(op, loc); + return addBranch(op, nullptr, loc); } TIntermBranch *TParseContext::addBranch(TOperator op, - TIntermTyped *returnValue, + TIntermTyped *expression, const TSourceLoc &loc) { - ASSERT(op == EOpReturn); - mFunctionReturnsValue = true; - if (mCurrentFunctionType->getBasicType() == EbtVoid) + if (expression != nullptr) { - error(loc, "void function cannot return a value", "return"); - recover(); + ASSERT(op == EOpReturn); + mFunctionReturnsValue = true; + if (mCurrentFunctionType->getBasicType() == EbtVoid) + { + error(loc, "void function cannot return a value", "return"); + } + else if (*mCurrentFunctionType != expression->getType()) + { + error(loc, "function return is not matching type:", "return"); + } } - else if (*mCurrentFunctionType != returnValue->getType()) - { - error(loc, "function return is not matching type:", "return"); - recover(); + TIntermBranch *node = new TIntermBranch(op, expression); + node->setLine(loc); + return node; +} + +void TParseContext::checkTextureGather(TIntermAggregate *functionCall) +{ + ASSERT(functionCall->getOp() == EOpCallBuiltInFunction); + const TString &name = functionCall->getFunctionSymbolInfo()->getName(); + bool isTextureGather = (name == "textureGather"); + bool isTextureGatherOffset = (name == "textureGatherOffset"); + if (isTextureGather || isTextureGatherOffset) + { + TIntermNode *componentNode = nullptr; + TIntermSequence *arguments = functionCall->getSequence(); + ASSERT(arguments->size() >= 2u && arguments->size() <= 4u); + const TIntermTyped *sampler = arguments->front()->getAsTyped(); + ASSERT(sampler != nullptr); + switch (sampler->getBasicType()) + { + case EbtSampler2D: + case EbtISampler2D: + case EbtUSampler2D: + case EbtSampler2DArray: + case EbtISampler2DArray: + case EbtUSampler2DArray: + if ((isTextureGather && arguments->size() == 3u) || + (isTextureGatherOffset && arguments->size() == 4u)) + { + componentNode = arguments->back(); + } + break; + case EbtSamplerCube: + case EbtISamplerCube: + case EbtUSamplerCube: + ASSERT(!isTextureGatherOffset); + if (arguments->size() == 3u) + { + componentNode = arguments->back(); + } + break; + case EbtSampler2DShadow: + case EbtSampler2DArrayShadow: + case EbtSamplerCubeShadow: + break; + default: + UNREACHABLE(); + break; + } + if (componentNode) + { + const TIntermConstantUnion *componentConstantUnion = + componentNode->getAsConstantUnion(); + if (componentNode->getAsTyped()->getQualifier() != EvqConst || !componentConstantUnion) + { + error(functionCall->getLine(), "Texture component must be a constant expression", + name.c_str()); + } + else + { + int component = componentConstantUnion->getIConst(0); + if (component < 0 || component > 3) + { + error(functionCall->getLine(), "Component must be in the range [0;3]", + name.c_str()); + } + } + } } - return intermediate.addBranch(op, returnValue, loc); } void TParseContext::checkTextureOffsetConst(TIntermAggregate *functionCall) { - ASSERT(!functionCall->isUserDefined()); - const TString &name = functionCall->getName(); + ASSERT(functionCall->getOp() == EOpCallBuiltInFunction); + const TString &name = functionCall->getFunctionSymbolInfo()->getName(); TIntermNode *offset = nullptr; TIntermSequence *arguments = functionCall->getSequence(); - if (name.compare(0, 16, "texelFetchOffset") == 0 || - name.compare(0, 16, "textureLodOffset") == 0 || - name.compare(0, 20, "textureProjLodOffset") == 0 || - name.compare(0, 17, "textureGradOffset") == 0 || - name.compare(0, 21, "textureProjGradOffset") == 0) + bool useTextureGatherOffsetConstraints = false; + if (name == "texelFetchOffset" || name == "textureLodOffset" || + name == "textureProjLodOffset" || name == "textureGradOffset" || + name == "textureProjGradOffset") { offset = arguments->back(); } - else if (name.compare(0, 13, "textureOffset") == 0 || - name.compare(0, 17, "textureProjOffset") == 0) + else if (name == "textureOffset" || name == "textureProjOffset") { // A bias parameter might follow the offset parameter. ASSERT(arguments->size() >= 3); offset = (*arguments)[2]; } + else if (name == "textureGatherOffset") + { + ASSERT(arguments->size() >= 3u); + const TIntermTyped *sampler = arguments->front()->getAsTyped(); + ASSERT(sampler != nullptr); + switch (sampler->getBasicType()) + { + case EbtSampler2D: + case EbtISampler2D: + case EbtUSampler2D: + case EbtSampler2DArray: + case EbtISampler2DArray: + case EbtUSampler2DArray: + offset = (*arguments)[2]; + break; + case EbtSampler2DShadow: + case EbtSampler2DArrayShadow: + offset = (*arguments)[3]; + break; + default: + UNREACHABLE(); + break; + } + useTextureGatherOffsetConstraints = true; + } if (offset != nullptr) { TIntermConstantUnion *offsetConstantUnion = offset->getAsConstantUnion(); if (offset->getAsTyped()->getQualifier() != EvqConst || !offsetConstantUnion) { - TString unmangledName = TFunction::unmangleName(name); error(functionCall->getLine(), "Texture offset must be a constant expression", - unmangledName.c_str()); - recover(); + name.c_str()); } else { ASSERT(offsetConstantUnion->getBasicType() == EbtInt); size_t size = offsetConstantUnion->getType().getObjectSize(); const TConstantUnion *values = offsetConstantUnion->getUnionArrayPointer(); + int minOffsetValue = useTextureGatherOffsetConstraints ? mMinProgramTextureGatherOffset + : mMinProgramTexelOffset; + int maxOffsetValue = useTextureGatherOffsetConstraints ? mMaxProgramTextureGatherOffset + : mMaxProgramTexelOffset; for (size_t i = 0u; i < size; ++i) { int offsetValue = values[i].getIConst(); - if (offsetValue > mMaxProgramTexelOffset || offsetValue < mMinProgramTexelOffset) + if (offsetValue > maxOffsetValue || offsetValue < minOffsetValue) { std::stringstream tokenStream; tokenStream << offsetValue; std::string token = tokenStream.str(); error(offset->getLine(), "Texture offset value out of valid range", token.c_str()); - recover(); } } } } } -TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunction *fnCall, - TIntermNode *paramNode, - TIntermNode *thisNode, - const TSourceLoc &loc, - bool *fatalError) +void TParseContext::checkAtomicMemoryBuiltinFunctions(TIntermAggregate *functionCall) { - *fatalError = false; - TOperator op = fnCall->getBuiltInOp(); - TIntermTyped *callNode = nullptr; - - if (thisNode != nullptr) + const TString &name = functionCall->getFunctionSymbolInfo()->getName(); + if (IsAtomicBuiltin(name)) { - TConstantUnion *unionArray = new TConstantUnion[1]; - int arraySize = 0; - TIntermTyped *typedThis = thisNode->getAsTyped(); - if (fnCall->getName() != "length") + TIntermSequence *arguments = functionCall->getSequence(); + TIntermTyped *memNode = (*arguments)[0]->getAsTyped(); + + if (IsBufferOrSharedVariable(memNode)) { - error(loc, "invalid method", fnCall->getName().c_str()); - recover(); + return; } - else if (paramNode != nullptr) + + while (memNode->getAsBinaryNode()) { - error(loc, "method takes no parameters", "length"); - recover(); + memNode = memNode->getAsBinaryNode()->getLeft(); + if (IsBufferOrSharedVariable(memNode)) + { + return; + } } - else if (typedThis == nullptr || !typedThis->isArray()) + + error(memNode->getLine(), + "The value passed to the mem argument of an atomic memory function does not " + "correspond to a buffer or shared variable.", + functionCall->getFunctionSymbolInfo()->getName().c_str()); + } +} + +// GLSL ES 3.10 Revision 4, 4.9 Memory Access Qualifiers +void TParseContext::checkImageMemoryAccessForBuiltinFunctions(TIntermAggregate *functionCall) +{ + ASSERT(functionCall->getOp() == EOpCallBuiltInFunction); + const TString &name = functionCall->getFunctionSymbolInfo()->getName(); + + if (name.compare(0, 5, "image") == 0) + { + TIntermSequence *arguments = functionCall->getSequence(); + TIntermTyped *imageNode = (*arguments)[0]->getAsTyped(); + + const TMemoryQualifier &memoryQualifier = imageNode->getMemoryQualifier(); + + if (name.compare(5, 5, "Store") == 0) { - error(loc, "length can only be called on arrays", "length"); - recover(); + if (memoryQualifier.readonly) + { + error(imageNode->getLine(), + "'imageStore' cannot be used with images qualified as 'readonly'", + GetImageArgumentToken(imageNode)); + } } - else + else if (name.compare(5, 4, "Load") == 0) { - arraySize = typedThis->getArraySize(); - if (typedThis->getAsSymbolNode() == nullptr) + if (memoryQualifier.writeonly) { - // This code path can be hit with expressions like these: - // (a = b).length() - // (func()).length() - // (int[3](0, 1, 2)).length() - // ESSL 3.00 section 5.9 defines expressions so that this is not actually a valid - // expression. - // It allows "An array name with the length method applied" in contrast to GLSL 4.4 - // spec section 5.9 which allows "An array, vector or matrix expression with the - // length method applied". - error(loc, "length can only be called on array names, not on array expressions", - "length"); - recover(); + error(imageNode->getLine(), + "'imageLoad' cannot be used with images qualified as 'writeonly'", + GetImageArgumentToken(imageNode)); } } - unionArray->setIConst(arraySize); - callNode = - intermediate.addConstantUnion(unionArray, TType(EbtInt, EbpUndefined, EvqConst), loc); } - else if (op != EOpNull) +} + +// GLSL ES 3.10 Revision 4, 13.51 Matching of Memory Qualifiers in Function Parameters +void TParseContext::checkImageMemoryAccessForUserDefinedFunctions( + const TFunction *functionDefinition, + const TIntermAggregate *functionCall) +{ + ASSERT(functionCall->getOp() == EOpCallFunctionInAST); + + const TIntermSequence &arguments = *functionCall->getSequence(); + + ASSERT(functionDefinition->getParamCount() == arguments.size()); + + for (size_t i = 0; i < arguments.size(); ++i) { - // - // Then this should be a constructor. - // Don't go through the symbol table for constructors. - // Their parameters will be verified algorithmically. - // - TType type(EbtVoid, EbpUndefined); // use this to get the type back - if (!constructorErrorCheck(loc, paramNode, *fnCall, op, &type)) - { - // - // It's a constructor, of type 'type'. - // - callNode = addConstructor(paramNode, &type, op, fnCall, loc); - } + TIntermTyped *typedArgument = arguments[i]->getAsTyped(); + const TType &functionArgumentType = typedArgument->getType(); + const TType &functionParameterType = *functionDefinition->getParam(i).type; + ASSERT(functionArgumentType.getBasicType() == functionParameterType.getBasicType()); - if (callNode == nullptr) + if (IsImage(functionArgumentType.getBasicType())) { - recover(); - callNode = intermediate.setAggregateOperator(nullptr, op, loc); + const TMemoryQualifier &functionArgumentMemoryQualifier = + functionArgumentType.getMemoryQualifier(); + const TMemoryQualifier &functionParameterMemoryQualifier = + functionParameterType.getMemoryQualifier(); + if (functionArgumentMemoryQualifier.readonly && + !functionParameterMemoryQualifier.readonly) + { + error(functionCall->getLine(), + "Function call discards the 'readonly' qualifier from image", + GetImageArgumentToken(typedArgument)); + } + + if (functionArgumentMemoryQualifier.writeonly && + !functionParameterMemoryQualifier.writeonly) + { + error(functionCall->getLine(), + "Function call discards the 'writeonly' qualifier from image", + GetImageArgumentToken(typedArgument)); + } + + if (functionArgumentMemoryQualifier.coherent && + !functionParameterMemoryQualifier.coherent) + { + error(functionCall->getLine(), + "Function call discards the 'coherent' qualifier from image", + GetImageArgumentToken(typedArgument)); + } + + if (functionArgumentMemoryQualifier.volatileQualifier && + !functionParameterMemoryQualifier.volatileQualifier) + { + error(functionCall->getLine(), + "Function call discards the 'volatile' qualifier from image", + GetImageArgumentToken(typedArgument)); + } } - callNode->setType(type); + } +} + +TIntermSequence *TParseContext::createEmptyArgumentsList() +{ + return new TIntermSequence(); +} + +TIntermTyped *TParseContext::addFunctionCallOrMethod(TFunction *fnCall, + TIntermSequence *arguments, + TIntermNode *thisNode, + const TSourceLoc &loc) +{ + if (thisNode != nullptr) + { + return addMethod(fnCall, arguments, thisNode, loc); + } + + TOperator op = fnCall->getBuiltInOp(); + if (op == EOpConstruct) + { + return addConstructor(arguments, fnCall->getReturnType(), loc); } else { - // - // Not a constructor. Find it in the symbol table. - // - const TFunction *fnCandidate; - bool builtIn; - fnCandidate = findFunction(loc, fnCall, mShaderVersion, &builtIn); - if (fnCandidate) + ASSERT(op == EOpNull); + return addNonConstructorFunctionCall(fnCall, arguments, loc); + } +} + +TIntermTyped *TParseContext::addMethod(TFunction *fnCall, + TIntermSequence *arguments, + TIntermNode *thisNode, + const TSourceLoc &loc) +{ + TIntermTyped *typedThis = thisNode->getAsTyped(); + // It's possible for the name pointer in the TFunction to be null in case it gets parsed as + // a constructor. But such a TFunction can't reach here, since the lexer goes into FIELDS + // mode after a dot, which makes type identifiers to be parsed as FIELD_SELECTION instead. + // So accessing fnCall->getName() below is safe. + if (fnCall->getName() != "length") + { + error(loc, "invalid method", fnCall->getName().c_str()); + } + else if (!arguments->empty()) + { + error(loc, "method takes no parameters", "length"); + } + else if (typedThis == nullptr || !typedThis->isArray()) + { + error(loc, "length can only be called on arrays", "length"); + } + else if (typedThis->getQualifier() == EvqPerVertexIn && + mGeometryShaderInputPrimitiveType == EptUndefined) + { + ASSERT(mShaderType == GL_GEOMETRY_SHADER_OES); + error(loc, "missing input primitive declaration before calling length on gl_in", "length"); + } + else + { + TIntermUnary *node = new TIntermUnary(EOpArrayLength, typedThis); + node->setLine(loc); + return node->fold(mDiagnostics); + } + return CreateZeroNode(TType(EbtInt, EbpUndefined, EvqConst)); +} + +TIntermTyped *TParseContext::addNonConstructorFunctionCall(TFunction *fnCall, + TIntermSequence *arguments, + const TSourceLoc &loc) +{ + // First find by unmangled name to check whether the function name has been + // hidden by a variable name or struct typename. + // If a function is found, check for one with a matching argument list. + bool builtIn; + const TSymbol *symbol = symbolTable.find(fnCall->getName(), mShaderVersion, &builtIn); + if (symbol != nullptr && !symbol->isFunction()) + { + error(loc, "function name expected", fnCall->getName().c_str()); + } + else + { + symbol = symbolTable.find(TFunction::GetMangledNameFromCall(fnCall->getName(), *arguments), + mShaderVersion, &builtIn); + if (symbol == nullptr) { + error(loc, "no matching overloaded function found", fnCall->getName().c_str()); + } + else + { + const TFunction *fnCandidate = static_cast<const TFunction *>(symbol); // // A declared function. // - if (builtIn && !fnCandidate->getExtension().empty() && - extensionErrorCheck(loc, fnCandidate->getExtension())) + if (builtIn && fnCandidate->getExtension() != TExtension::UNDEFINED) { - recover(); + checkCanUseExtension(loc, fnCandidate->getExtension()); } - op = fnCandidate->getBuiltInOp(); + TOperator op = fnCandidate->getBuiltInOp(); if (builtIn && op != EOpNull) { - // // A function call mapped to a built-in operation. - // if (fnCandidate->getParamCount() == 1) { - // // Treat it like a built-in unary operator. - // - TIntermAggregate *paramAgg = paramNode->getAsAggregate(); - paramNode = paramAgg->getSequence()->front(); - callNode = createUnaryMath(op, paramNode->getAsTyped(), loc, - &fnCandidate->getReturnType()); - if (callNode == nullptr) - { - std::stringstream extraInfoStream; - extraInfoStream - << "built in unary operator function. Type: " - << static_cast<TIntermTyped *>(paramNode)->getCompleteString(); - std::string extraInfo = extraInfoStream.str(); - error(paramNode->getLine(), " wrong operand type", "Internal Error", - extraInfo.c_str()); - *fatalError = true; - return nullptr; - } + TIntermNode *unaryParamNode = arguments->front(); + TIntermTyped *callNode = createUnaryMath(op, unaryParamNode->getAsTyped(), loc); + ASSERT(callNode != nullptr); + return callNode; } else { - TIntermAggregate *aggregate = - intermediate.setAggregateOperator(paramNode, op, loc); - aggregate->setType(fnCandidate->getReturnType()); - aggregate->setPrecisionFromChildren(); - if (aggregate->areChildrenConstQualified()) - { - aggregate->getTypePointer()->setQualifier(EvqConst); - } + TIntermAggregate *callNode = + TIntermAggregate::Create(fnCandidate->getReturnType(), op, arguments); + callNode->setLine(loc); // Some built-in functions have out parameters too. - functionCallLValueErrorCheck(fnCandidate, aggregate); + functionCallRValueLValueErrorCheck(fnCandidate, callNode); - // See if we can constant fold a built-in. Note that this may be possible even - // if it is not const-qualified. - TIntermTyped *foldedNode = intermediate.foldAggregateBuiltIn(aggregate); - if (foldedNode) + if (TIntermAggregate::CanFoldAggregateBuiltInOp(callNode->getOp())) { - callNode = foldedNode; + // See if we can constant fold a built-in. Note that this may be possible + // even if it is not const-qualified. + return callNode->fold(mDiagnostics); } else { - callNode = aggregate; + return callNode; } } } else { // This is a real function call - TIntermAggregate *aggregate = - intermediate.setAggregateOperator(paramNode, EOpFunctionCall, loc); - aggregate->setType(fnCandidate->getReturnType()); - - // this is how we know whether the given function is a builtIn function or a user - // defined function - // if builtIn == false, it's a userDefined -> could be an overloaded - // builtIn function also - // if builtIn == true, it's definitely a builtIn function with EOpNull - if (!builtIn) - aggregate->setUserDefined(); - aggregate->setName(fnCandidate->getMangledName()); - aggregate->setFunctionId(fnCandidate->getUniqueId()); - - // This needs to happen after the name is set + TIntermAggregate *callNode = nullptr; + + // If builtIn == false, the function is user defined - could be an overloaded + // built-in as well. + // if builtIn == true, it's a builtIn function with no op associated with it. + // This needs to happen after the function info including name is set. if (builtIn) { - aggregate->setBuiltInFunctionPrecision(); - - checkTextureOffsetConst(aggregate); + callNode = TIntermAggregate::CreateBuiltInFunctionCall(*fnCandidate, arguments); + checkTextureOffsetConst(callNode); + checkTextureGather(callNode); + checkImageMemoryAccessForBuiltinFunctions(callNode); + checkAtomicMemoryBuiltinFunctions(callNode); + } + else + { + callNode = TIntermAggregate::CreateFunctionCall(*fnCandidate, arguments); + checkImageMemoryAccessForUserDefinedFunctions(fnCandidate, callNode); } - callNode = aggregate; + functionCallRValueLValueErrorCheck(fnCandidate, callNode); - functionCallLValueErrorCheck(fnCandidate, aggregate); + callNode->setLine(loc); + + return callNode; } } - else - { - // error message was put out by findFunction() - // Put on a dummy node for error recovery - TConstantUnion *unionArray = new TConstantUnion[1]; - unionArray->setFConst(0.0f); - callNode = intermediate.addConstantUnion(unionArray, - TType(EbtFloat, EbpUndefined, EvqConst), loc); - recover(); - } } - return callNode; + + // Error message was already written. Put on a dummy node for error recovery. + return CreateZeroNode(TType(EbtFloat, EbpMedium, EvqConst)); } TIntermTyped *TParseContext::addTernarySelection(TIntermTyped *cond, - TIntermTyped *trueBlock, - TIntermTyped *falseBlock, + TIntermTyped *trueExpression, + TIntermTyped *falseExpression, const TSourceLoc &loc) { - if (boolErrorCheck(loc, cond)) - recover(); + if (!checkIsScalarBool(loc, cond)) + { + return falseExpression; + } - if (trueBlock->getType() != falseBlock->getType()) + if (trueExpression->getType() != falseExpression->getType()) { - binaryOpError(loc, ":", trueBlock->getCompleteString(), falseBlock->getCompleteString()); - recover(); - return falseBlock; + std::stringstream reasonStream; + reasonStream << "mismatching ternary operator operand types '" + << trueExpression->getCompleteString() << " and '" + << falseExpression->getCompleteString() << "'"; + std::string reason = reasonStream.str(); + error(loc, reason.c_str(), "?:"); + return falseExpression; + } + if (IsOpaqueType(trueExpression->getBasicType())) + { + // ESSL 1.00 section 4.1.7 + // ESSL 3.00.6 section 4.1.7 + // Opaque/sampler types are not allowed in most types of expressions, including ternary. + // Note that structs containing opaque types don't need to be checked as structs are + // forbidden below. + error(loc, "ternary operator is not allowed for opaque types", "?:"); + return falseExpression; + } + + if (cond->getMemoryQualifier().writeonly || trueExpression->getMemoryQualifier().writeonly || + falseExpression->getMemoryQualifier().writeonly) + { + error(loc, "ternary operator is not allowed for variables with writeonly", "?:"); + return falseExpression; } - // ESSL1 sections 5.2 and 5.7: - // ESSL3 section 5.7: + + // ESSL 1.00.17 sections 5.2 and 5.7: // Ternary operator is not among the operators allowed for structures/arrays. - if (trueBlock->isArray() || trueBlock->getBasicType() == EbtStruct) + // ESSL 3.00.6 section 5.7: + // Ternary operator support is optional for arrays. No certainty that it works across all + // devices with struct either, so we err on the side of caution here. TODO (oetuaho@nvidia.com): + // Would be nice to make the spec and implementation agree completely here. + if (trueExpression->isArray() || trueExpression->getBasicType() == EbtStruct) { - error(loc, "ternary operator is not allowed for structures or arrays", ":"); - recover(); - return falseBlock; + error(loc, "ternary operator is not allowed for structures or arrays", "?:"); + return falseExpression; } - return intermediate.addSelection(cond, trueBlock, falseBlock, loc); + if (trueExpression->getBasicType() == EbtInterfaceBlock) + { + error(loc, "ternary operator is not allowed for interface blocks", "?:"); + return falseExpression; + } + + // WebGL2 section 5.26, the following results in an error: + // "Ternary operator applied to void, arrays, or structs containing arrays" + if (mShaderSpec == SH_WEBGL2_SPEC && trueExpression->getBasicType() == EbtVoid) + { + error(loc, "ternary operator is not allowed for void", "?:"); + return falseExpression; + } + + // Note that the node resulting from here can be a constant union without being qualified as + // constant. + TIntermTernary *node = new TIntermTernary(cond, trueExpression, falseExpression); + node->setLine(loc); + + return node->fold(); } // @@ -4128,7 +6004,7 @@ int PaParseStrings(size_t count, const int length[], TParseContext *context) { - if ((count == 0) || (string == NULL)) + if ((count == 0) || (string == nullptr)) return 1; if (glslang_initialize(context)) @@ -4142,3 +6018,5 @@ int PaParseStrings(size_t count, return (error == 0) && (context->numErrors() == 0) ? 0 : 1; } + +} // namespace sh |