summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/angle/src/compiler/translator/ParseContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/angle/src/compiler/translator/ParseContext.cpp')
-rw-r--r--src/3rdparty/angle/src/compiler/translator/ParseContext.cpp6080
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 &param = 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 &param = 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 &param = 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 &param = 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