3893 lines
157 KiB
C++
Raw Blame History

//
//Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
//Copyright (C) 2012-2013 LunarG, Inc.
//
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without
//modification, are permitted provided that the following conditions
//are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//POSSIBILITY OF SUCH DAMAGE.
//
#include "ParseHelper.h"
#include "Scan.h"
#include "osinclude.h"
#include <stdarg.h>
#include <algorithm>
#include "preprocessor/PpContext.h"
extern int yyparse(void*);
namespace glslang {
TParseContext::TParseContext(TSymbolTable& symt, TIntermediate& interm, bool pb, int v, EProfile p, EShLanguage L, TInfoSink& is,
bool fc, EShMessages m) :
intermediate(interm), symbolTable(symt), infoSink(is), language(L),
version(v), profile(p), forwardCompatible(fc), messages(m),
contextPragma(true, false), loopNestingLevel(0), controlFlowNestingLevel(0), structNestingLevel(0),
tokensBeforeEOF(false), limits(resources.limits), currentScanner(0),
numErrors(0), parsingBuiltins(pb), afterEOF(false),
anyIndexLimits(false)
{
// ensure we always have a linkage node, even if empty, to simplify tree topology algorithms
linkage = new TIntermAggregate;
// set all precision defaults to EpqNone, which is correct for all desktop types
// and for ES types that don't have defaults (thus getting an error on use)
for (int type = 0; type < EbtNumTypes; ++type)
defaultPrecision[type] = EpqNone;
for (int type = 0; type < maxSamplerIndex; ++type)
defaultSamplerPrecision[type] = EpqNone;
// replace with real defaults for those that have them
if (profile == EEsProfile) {
TSampler sampler;
sampler.set(EbtFloat, Esd2D);
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
sampler.set(EbtFloat, EsdCube);
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
sampler.set(EbtFloat, Esd2D);
sampler.external = true;
defaultSamplerPrecision[computeSamplerTypeIndex(sampler)] = EpqLow;
switch (language) {
case EShLangFragment:
defaultPrecision[EbtInt] = EpqMedium;
defaultPrecision[EbtUint] = EpqMedium;
defaultPrecision[EbtSampler] = EpqLow;
break;
default:
defaultPrecision[EbtInt] = EpqHigh;
defaultPrecision[EbtUint] = EpqHigh;
defaultPrecision[EbtFloat] = EpqHigh;
defaultPrecision[EbtSampler] = EpqLow;
break;
}
}
globalUniformDefaults.clear();
globalUniformDefaults.layoutMatrix = ElmColumnMajor;
globalUniformDefaults.layoutPacking = ElpShared;
globalBufferDefaults.clear();
globalBufferDefaults.layoutMatrix = ElmColumnMajor;
globalBufferDefaults.layoutPacking = ElpShared;
globalInputDefaults.clear();
globalOutputDefaults.clear();
if (language == EShLangGeometry)
globalOutputDefaults.layoutStream = 0;
}
void TParseContext::setLimits(const TBuiltInResource& r)
{
resources = r;
anyIndexLimits = ! limits.generalAttributeMatrixVectorIndexing ||
! limits.generalConstantMatrixVectorIndexing ||
! limits.generalSamplerIndexing ||
! limits.generalUniformIndexing ||
! limits.generalVariableIndexing ||
! limits.generalVaryingIndexing;
}
//
// Parse an array of strings using yyparse, going through the
// preprocessor to tokenize the shader strings, then through
// the GLSL scanner.
//
// Returns true for successful acceptance of the shader, false if any errors.
//
bool TParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner& input, bool versionWillBeError)
{
currentScanner = &input;
ppContext.setInput(input, versionWillBeError);
yyparse((void*)this);
finalErrorCheck();
return numErrors == 0;
}
// This is called from bison when it has a parse (syntax) error
void TParseContext::parserError(const char *s)
{
if (afterEOF) {
if (tokensBeforeEOF == 1)
error(getCurrentLoc(), "", "pre-mature EOF", s, "");
} else
error(getCurrentLoc(), "", "", s, "");
}
void TParseContext::handlePragma(TSourceLoc loc, const TVector<TString>& tokens)
{
if (tokens.size() == 0)
return;
if (tokens[0].compare("optimize") == 0) {
if (tokens.size() != 4) {
error(loc, "optimize pragma syntax is incorrect", "#pragma", "");
return;
}
if (tokens[1].compare("(") != 0) {
error(loc, "\"(\" expected after 'optimize' keyword", "#pragma", "");
return;
}
if (tokens[2].compare("on") == 0)
contextPragma.optimize = true;
else if (tokens[2].compare("off") == 0)
contextPragma.optimize = false;
else {
error(loc, "\"on\" or \"off\" expected after '(' for 'optimize' pragma", "#pragma", "");
return;
}
if (tokens[3].compare(")") != 0) {
error(loc, "\")\" expected to end 'optimize' pragma", "#pragma", "");
return;
}
} else if (tokens[0].compare("debug") == 0) {
if (tokens.size() != 4) {
error(loc, "debug pragma syntax is incorrect", "#pragma", "");
return;
}
if (tokens[1].compare("(") != 0) {
error(loc, "\"(\" expected after 'debug' keyword", "#pragma", "");
return;
}
if (tokens[2].compare("on") == 0)
contextPragma.debug = true;
else if (tokens[2].compare("off") == 0)
contextPragma.debug = false;
else {
error(loc, "\"on\" or \"off\" expected after '(' for 'debug' pragma", "#pragma", "");
return;
}
if (tokens[3].compare(")") != 0) {
error(loc, "\")\" expected to end 'debug' pragma", "#pragma", "");
return;
}
}
}
///////////////////////////////////////////////////////////////////////
//
// Sub- vector and matrix fields
//
////////////////////////////////////////////////////////////////////////
//
// Look at a '.' field selector string and change it into offsets
// for a vector or scalar
//
// Returns true if there is no error.
//
bool TParseContext::parseVectorFields(TSourceLoc loc, const TString& compString, int vecSize, TVectorFields& fields)
{
fields.num = (int) compString.size();
if (fields.num > 4) {
error(loc, "illegal vector field selection", compString.c_str(), "");
return false;
}
enum {
exyzw,
ergba,
estpq,
} fieldSet[4];
for (int i = 0; i < fields.num; ++i) {
switch (compString[i]) {
case 'x':
fields.offsets[i] = 0;
fieldSet[i] = exyzw;
break;
case 'r':
fields.offsets[i] = 0;
fieldSet[i] = ergba;
break;
case 's':
fields.offsets[i] = 0;
fieldSet[i] = estpq;
break;
case 'y':
fields.offsets[i] = 1;
fieldSet[i] = exyzw;
break;
case 'g':
fields.offsets[i] = 1;
fieldSet[i] = ergba;
break;
case 't':
fields.offsets[i] = 1;
fieldSet[i] = estpq;
break;
case 'z':
fields.offsets[i] = 2;
fieldSet[i] = exyzw;
break;
case 'b':
fields.offsets[i] = 2;
fieldSet[i] = ergba;
break;
case 'p':
fields.offsets[i] = 2;
fieldSet[i] = estpq;
break;
case 'w':
fields.offsets[i] = 3;
fieldSet[i] = exyzw;
break;
case 'a':
fields.offsets[i] = 3;
fieldSet[i] = ergba;
break;
case 'q':
fields.offsets[i] = 3;
fieldSet[i] = estpq;
break;
default:
error(loc, "illegal vector field selection", compString.c_str(), "");
return false;
}
}
for (int i = 0; i < fields.num; ++i) {
if (fields.offsets[i] >= vecSize) {
error(loc, "vector field selection out of range", compString.c_str(), "");
return false;
}
if (i > 0) {
if (fieldSet[i] != fieldSet[i-1]) {
error(loc, "illegal - vector component fields not from the same set", compString.c_str(), "");
return false;
}
}
}
return true;
}
///////////////////////////////////////////////////////////////////////
//
// Errors
//
////////////////////////////////////////////////////////////////////////
//
// Used to output syntax, parsing, and semantic errors.
//
void C_DECL TParseContext::error(TSourceLoc loc, const char *szReason, const char *szToken,
const char *szExtraInfoFormat, ...)
{
const int maxSize = GlslangMaxTokenLength + 200;
char szExtraInfo[maxSize];
va_list marker;
va_start(marker, szExtraInfoFormat);
safe_vsprintf(szExtraInfo, maxSize, szExtraInfoFormat, marker);
infoSink.info.prefix(EPrefixError);
infoSink.info.location(loc);
infoSink.info << "'" << szToken << "' : " << szReason << " " << szExtraInfo << "\n";
va_end(marker);
++numErrors;
}
void C_DECL TParseContext::warn(TSourceLoc loc, const char *szReason, const char *szToken,
const char *szExtraInfoFormat, ...)
{
if (messages & EShMsgSuppressWarnings)
return;
const int maxSize = GlslangMaxTokenLength + 200;
char szExtraInfo[maxSize];
va_list marker;
va_start(marker, szExtraInfoFormat);
safe_vsprintf(szExtraInfo, maxSize, szExtraInfoFormat, marker);
infoSink.info.prefix(EPrefixWarning);
infoSink.info.location(loc);
infoSink.info << "'" << szToken << "' : " << szReason << " " << szExtraInfo << "\n";
va_end(marker);
}
//
// Handle seeing a variable identifier in the grammar.
//
TIntermTyped* TParseContext::handleVariable(TSourceLoc loc, TSymbol* symbol, TString* string)
{
TIntermTyped* node = 0;
bool noteAccess = false;
// Error check for function requiring specific extensions present.
if (symbol && symbol->getNumExtensions())
requireExtensions(loc, symbol->getNumExtensions(), symbol->getExtensions(), symbol->getName().c_str());
const TAnonMember* anon = symbol ? symbol->getAsAnonMember() : 0;
if (anon) {
// it was a member of an anonymous container, have to insert its dereference
const TVariable* variable = anon->getAnonContainer().getAsVariable();
TIntermTyped* container = intermediate.addSymbol(variable->getUniqueId(), variable->getName(), variable->getType(), loc);
TConstUnionArray unionArray(1);
unionArray[0].setUConst(anon->getMemberNumber());
TIntermTyped* constNode = intermediate.addConstantUnion(unionArray, TType(EbtUint, EvqConst), loc);
node = intermediate.addIndex(EOpIndexDirectStruct, container, constNode, loc);
node->setType(*(*variable->getType().getStruct())[anon->getMemberNumber()].type);
if (node->getBasicType() == EbtVoid)
error(loc, "member of nameless block was not redeclared", string->c_str(), "");
if (variable->getType().getQualifier().isIo())
noteAccess = true;
// TODO: does this create any accidental type sharing with the built-in level?
} else {
// The symbol table search was done in the lexical phase, but
// if this is a new symbol, it wouldn't have found it.
TVariable* variable = symbol ? symbol->getAsVariable() : 0;
if (symbol && ! variable)
error(loc, "variable name expected", string->c_str(), "");
if (! variable)
variable = new TVariable(string, TType(EbtVoid));
if (variable->getType().getQualifier().storage == EvqConst)
node = intermediate.addConstantUnion(variable->getConstArray(), variable->getType(), loc);
else {
TType* type;
if (variable->isReadOnly()) {
type = new TType;
// break type sharing with built-ins; only costs if there are arrays or structures
type->deepCopy(variable->getType());
} else
type = &variable->getWritableType();
node = intermediate.addSymbol(variable->getUniqueId(), variable->getName(), *type, loc);
if (type->getQualifier().isIo())
noteAccess = true;
}
}
if (noteAccess)
intermediate.addIoAccessed(*string);
return node;
}
//
// Handle seeing a base[index] dereference in the grammar.
//
TIntermTyped* TParseContext::handleBracketDereference(TSourceLoc loc, TIntermTyped* base, TIntermTyped* index)
{
TIntermTyped* result = 0;
int indexValue = 0;
if (index->getQualifier().storage == EvqConst) {
indexValue = index->getAsConstantUnion()->getConstArray()[0].getIConst();
checkIndex(loc, base->getType(), indexValue);
}
variableCheck(base);
if (! base->isArray() && ! base->isMatrix() && ! base->isVector()) {
if (base->getAsSymbolNode())
error(loc, " left of '[' is not of type array, matrix, or vector ", base->getAsSymbolNode()->getName().c_str(), "");
else
error(loc, " left of '[' is not of type array, matrix, or vector ", "expression", "");
} else if (base->getType().getQualifier().storage == EvqConst && index->getQualifier().storage == EvqConst)
return intermediate.foldDereference(base, indexValue, loc);
else {
// at least one of base and index is variable...
if (index->getQualifier().storage == EvqConst) {
if (base->isArray() && base->getType().getArraySize() == 0)
updateMaxArraySize(loc, base, indexValue);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
} else {
if (base->isArray() && base->getType().getArraySize() == 0)
error(loc, "", "[", "array must be redeclared with a size before being indexed with a variable");
if (base->getBasicType() == EbtBlock)
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing block array");
else if (language == EShLangFragment && base->getQualifier().isPipeOutput())
requireProfile(base->getLoc(), ~EEsProfile, "variable indexing fragment shader ouput array");
else if (base->getBasicType() == EbtSampler && version >= 130) {
const char* explanation = "variable indexing sampler array";
requireProfile(base->getLoc(), ECoreProfile | ECompatibilityProfile, explanation);
profileRequires(base->getLoc(), ECoreProfile | ECompatibilityProfile, 400, 0, explanation);
}
result = intermediate.addIndex(EOpIndexIndirect, base, index, loc);
}
}
if (result == 0) {
// Insert dummy error-recovery result
TConstUnionArray unionArray(1);
unionArray[0].setDConst(0.0);
result = intermediate.addConstantUnion(unionArray, TType(EbtFloat, EvqConst), loc);
} else {
// Insert valid dereferenced result
TType newType(base->getType(), 0); // dereferenced type
if (base->getType().getQualifier().storage == EvqConst && index->getQualifier().storage == EvqConst)
newType.getQualifier().storage = EvqConst;
else
newType.getQualifier().storage = EvqTemporary;
result->setType(newType);
if (anyIndexLimits)
handleIndexLimits(loc, base, index);
if (language == EShLangGeometry && base->isArray())
handleInputArrayAccess(loc, base);
}
return result;
}
void TParseContext::checkIndex(TSourceLoc loc, const TType& type, int& index)
{
if (index < 0) {
error(loc, "", "[", "index out of range '%d'", index);
index = 0;
} else if (type.isArray()) {
if (type.getArraySize() != 0 && index >= type.getArraySize()) {
error(loc, "", "[", "array index out of range '%d'", index);
index = type.getArraySize() - 1;
}
} else if (type.isVector()) {
if (index >= type.getVectorSize()) {
error(loc, "", "[", "vector index out of range '%d'", index);
index = type.getVectorSize() - 1;
}
} else if (type.isMatrix()) {
if (index >= type.getMatrixCols()) {
error(loc, "", "[", "matrix index out of range '%d'", index);
index = type.getMatrixCols() - 1;
}
}
}
// for ES 2.0 (version 100) limitations for almost all index operations except vertex-shader uniforms
void TParseContext::handleIndexLimits(TSourceLoc loc, TIntermTyped* base, TIntermTyped* index)
{
if ((! limits.generalSamplerIndexing && base->getBasicType() == EbtSampler) ||
(! limits.generalUniformIndexing && base->getQualifier().isUniform() && language != EShLangVertex) ||
(! limits.generalAttributeMatrixVectorIndexing && base->getQualifier().isPipeInput() && language == EShLangVertex && (base->getType().isMatrix() || base->getType().isVector())) ||
(! limits.generalConstantMatrixVectorIndexing && base->getAsConstantUnion()) ||
(! limits.generalVariableIndexing && ! base->getType().getQualifier().isUniform() &&
! base->getType().getQualifier().isPipeInput() &&
! base->getType().getQualifier().isPipeOutput() &&
base->getType().getQualifier().storage != EvqConst) ||
(! limits.generalVaryingIndexing && (base->getType().getQualifier().isPipeInput() ||
base->getType().getQualifier().isPipeOutput()))) {
// it's too early to know what the inductive variables are, save it for post processing
needsIndexLimitationChecking.push_back(index);
}
}
// Handle a dereference of a geometry shader input arrays.
// See inputArrayNodeResizeList comment in ParseHelper.h.
//
void TParseContext::handleInputArrayAccess(TSourceLoc loc, TIntermTyped* base)
{
if (base->getType().getQualifier().storage == EvqVaryingIn) {
TIntermSymbol* symbol = base->getAsSymbolNode();
assert(symbol);
inputArrayNodeResizeList.push_back(symbol);
if (symbol && builtInName(symbol->getName())) {
// make sure we have a user-modifiable copy of this built-in input array
TSymbol* input = symbolTable.find(symbol->getName());
if (input->isReadOnly()) {
input = symbolTable.copyUp(input);
inputArraySymbolResizeList.push_back(input);
// Save it in the AST for linker use.
intermediate.addSymbolLinkageNode(linkage, *input);
}
}
}
}
// If there has been an input primitive declaration, make sure all input array types
// match it in size. Types come either from nodes in the AST or symbols in the
// symbol table.
//
// Types without an array size will be given one.
// Types already having a size that is wrong will get an error.
//
void TParseContext::checkInputArrayConsistency(TSourceLoc loc, bool tailOnly)
{
TLayoutGeometry primitive = intermediate.getInputPrimitive();
if (primitive == ElgNone)
return;
if (tailOnly) {
checkInputArrayConsistency(loc, primitive, inputArraySymbolResizeList.back()->getWritableType(), inputArraySymbolResizeList.back()->getName());
return;
}
for (size_t i = 0; i < inputArrayNodeResizeList.size(); ++i)
checkInputArrayConsistency(loc, primitive, inputArrayNodeResizeList[i]->getWritableType(), inputArrayNodeResizeList[i]->getName());
for (size_t i = 0; i < inputArraySymbolResizeList.size(); ++i)
checkInputArrayConsistency(loc, primitive, inputArraySymbolResizeList[i]->getWritableType(), inputArraySymbolResizeList[i]->getName());
}
void TParseContext::checkInputArrayConsistency(TSourceLoc loc, TLayoutGeometry primitive, TType& type, const TString& name)
{
int requiredSize = TQualifier::mapGeometryToSize(primitive);
if (type.getArraySize() == 0)
type.changeArraySize(requiredSize);
else if (type.getArraySize() != requiredSize)
error(loc, "inconsistent input primitive for array size", TQualifier::getGeometryString(primitive), name.c_str());
}
//
// Handle seeing a base.field dereference in the grammar.
//
TIntermTyped* TParseContext::handleDotDereference(TSourceLoc loc, TIntermTyped* base, TString& field)
{
TIntermTyped* result = base;
variableCheck(base);
if (base->isArray()) {
//
// It can only be a method (e.g., length), which can't be resolved until
// we later see the function calling syntax. Save away the name for now.
//
if (field == "length") {
profileRequires(loc, ENoProfile, 120, GL_3DL_array_objects, ".length");
profileRequires(loc, EEsProfile, 300, 0, ".length");
result = intermediate.addMethod(base, TType(EbtInt), &field, loc);
} else
error(loc, "only the length method is supported for array", field.c_str(), "");
} else if (base->isVector() || base->isScalar()) {
if (base->isScalar()) {
const char* dotFeature = "scalar swizzle";
requireProfile(loc, ECoreProfile | ECompatibilityProfile, dotFeature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, GL_ARB_shading_language_420pack, dotFeature);
}
TVectorFields fields;
if (! parseVectorFields(loc, field, base->getVectorSize(), fields)) {
fields.num = 1;
fields.offsets[0] = 0;
}
if (base->isScalar()) {
if (fields.num == 1)
return result;
else {
TType type(base->getBasicType(), EvqTemporary, fields.num);
return addConstructor(loc, base, type, mapTypeToConstructorOp(type));
}
}
if (base->getType().getQualifier().storage == EvqConst)
result = intermediate.foldSwizzle(base, fields, loc);
else {
if (fields.num == 1) {
TConstUnionArray unionArray(1);
unionArray[0].setIConst(fields.offsets[0]);
TIntermTyped* index = intermediate.addConstantUnion(unionArray, TType(EbtInt, EvqConst), loc);
result = intermediate.addIndex(EOpIndexDirect, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision));
} else {
TString vectorString = field;
TIntermTyped* index = intermediate.addSwizzle(fields, loc);
result = intermediate.addIndex(EOpVectorSwizzle, base, index, loc);
result->setType(TType(base->getBasicType(), EvqTemporary, base->getType().getQualifier().precision, (int) vectorString.size()));
}
}
} else if (base->isMatrix())
error(loc, "field selection not allowed on matrix", ".", "");
else if (base->getBasicType() == EbtStruct || base->getBasicType() == EbtBlock) {
TTypeList* fields = base->getType().getStruct();
bool fieldFound = false;
unsigned int member;
for (member = 0; member < fields->size(); ++member) {
if ((*fields)[member].type->getFieldName() == field) {
fieldFound = true;
break;
}
}
if (fieldFound) {
if (base->getType().getQualifier().storage == EvqConst)
result = intermediate.foldDereference(base, member, loc);
else {
TConstUnionArray unionArray(1);
unionArray[0].setIConst(member);
TIntermTyped* index = intermediate.addConstantUnion(unionArray, TType(EbtInt, EvqConst), loc);
result = intermediate.addIndex(EOpIndexDirectStruct, base, index, loc);
result->setType(*(*fields)[member].type);
}
} else
error(loc, " no such field in structure", field.c_str(), "");
} else
error(loc, " dot operator does not operater on this type:", field.c_str(), base->getType().getCompleteString().c_str());
return result;
}
//
// Handle seeing a function declarator in the grammar. This is the precursor
// to recognizing a function prototype or function definition.
//
TFunction* TParseContext::handleFunctionDeclarator(TSourceLoc loc, TFunction& function, bool prototype)
{
// ES can't declare prototypes inside functions
if (! symbolTable.atGlobalLevel())
requireProfile(loc, ~EEsProfile, "local function declaration");
//
// Multiple declarations of the same function name are allowed.
//
// If this is a definition, the definition production code will check for redefinitions
// (we don't know at this point if it's a definition or not).
//
// Redeclarations (full signature match) are allowed. But, return types and parameter qualifiers must also match.
// - except ES 100, which only allows a single prototype
//
// ES 100 does not allow redefining, but does allow overloading of built-in functions.
// ES 300 does not allow redefining or overloading of built-in functions.
//
bool builtIn;
TSymbol* symbol = symbolTable.find(function.getMangledName(), &builtIn);
if (symbol && symbol->getAsFunction() && builtIn)
requireProfile(loc, ~EEsProfile, "redefinition of built-in function");
const TFunction* prevDec = symbol ? symbol->getAsFunction() : 0;
if (prevDec) {
if (prevDec->isPrototyped() && prototype)
profileRequires(loc, EEsProfile, 300, 0, "multiple prototypes for same function");
if (prevDec->getType() != function.getType())
error(loc, "overloaded functions must have the same return type", function.getType().getBasicTypeString().c_str(), "");
for (int i = 0; i < prevDec->getParamCount(); ++i) {
if ((*prevDec)[i].type->getQualifier().storage != function[i].type->getQualifier().storage)
error(loc, "overloaded functions must have the same parameter storage qualifiers for argument", function[i].type->getStorageQualifierString(), "%d", i+1);
if ((*prevDec)[i].type->getQualifier().precision != function[i].type->getQualifier().precision)
error(loc, "overloaded functions must have the same parameter precision qualifiers for argument", function[i].type->getPrecisionQualifierString(), "%d", i+1);
}
}
arrayObjectCheck(loc, function.getType(), "array in function return type");
if (prototype) {
// All built-in functions are defined, even though they don't have a body.
// Count their prototype as a definition instead.
if (symbolTable.atBuiltInLevel())
function.setDefined();
else {
if (prevDec && ! builtIn)
symbol->getAsFunction()->setPrototyped(); // need a writable one, but like having prevDec as a const
function.setPrototyped();
}
}
// This insert won't actually insert it if it's a duplicate signature, but it will still check for
// other forms of name collisions.
if (! symbolTable.insert(function))
error(loc, "function name is redeclaration of existing name", function.getName().c_str(), "");
//
// If this is a redeclaration, it could also be a definition,
// in which case, we need to use the parameter names from this one, and not the one that's
// being redeclared. So, pass back this declaration, not the one in the symbol table.
//
return &function;
}
//
// Handle seeing the function prototype in front of a function definition in the grammar.
// The body is handled after this function returns.
//
TIntermAggregate* TParseContext::handleFunctionDefinition(TSourceLoc loc, TFunction& function)
{
currentCaller = function.getMangledName();
TSymbol* symbol = symbolTable.find(function.getMangledName());
TFunction* prevDec = symbol ? symbol->getAsFunction() : 0;
if (! prevDec)
error(loc, "can't find function", function.getName().c_str(), "");
// 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 && prevDec->isDefined()) {
// Then this function already has a body.
error(loc, "function already has a body", function.getName().c_str(), "");
}
if (prevDec && ! prevDec->isDefined()) {
prevDec->setDefined();
// Remember the return type for later checking for RETURN statements.
currentFunctionType = &(prevDec->getType());
} else
currentFunctionType = new TType(EbtVoid);
functionReturnsValue = false;
//
// Raise error message if main function takes any parameters or returns anything other than void
//
if (function.getName() == "main") {
if (function.getParamCount() > 0)
error(loc, "function cannot take any parameter(s)", function.getName().c_str(), "");
if (function.getType().getBasicType() != EbtVoid)
error(loc, "", function.getType().getBasicTypeString().c_str(), "main function cannot return a value");
intermediate.addMainCount();
}
//
// New symbol table scope for body of function plus its arguments
//
symbolTable.push();
//
// 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 (int i = 0; i < function.getParamCount(); i++) {
TParameter& param = function[i];
if (param.name != 0) {
TVariable *variable = new TVariable(param.name, *param.type);
// Insert the parameters with name in the symbol table.
if (! symbolTable.insert(*variable))
error(loc, "redefinition", variable->getName().c_str(), "");
else {
// Transfer ownership of name pointer to symbol table.
param.name = 0;
// Add the parameter to the HIL
paramNodes = intermediate.growAggregate(paramNodes,
intermediate.addSymbol(variable->getUniqueId(),
variable->getName(),
variable->getType(), loc),
loc);
}
} else
paramNodes = intermediate.growAggregate(paramNodes, intermediate.addSymbol(0, "", *param.type, loc), loc);
}
intermediate.setAggregateOperator(paramNodes, EOpParameters, TType(EbtVoid), loc);
loopNestingLevel = 0;
controlFlowNestingLevel = 0;
return paramNodes;
}
//
// Handle seeing function call syntax in the grammar, which could be any of
// - .length() method
// - constructor
// - a call to a built-in function mapped to an operator
// - a call to a built-in function that will remain a function call (e.g., texturing)
// - user function
// - subroutine call (not implemented yet)
//
TIntermTyped* TParseContext::handleFunctionCall(TSourceLoc loc, TFunction* fnCall, TIntermNode* intermNode, TIntermAggregate* intermAggregate)
{
TIntermTyped* result = 0;
TOperator op = fnCall->getBuiltInOp();
if (op == EOpArrayLength) {
if (fnCall->getParamCount() > 0)
error(loc, "method does not accept any arguments", fnCall->getName().c_str(), "");
int length;
if (intermNode->getAsTyped() == 0 || ! intermNode->getAsTyped()->getType().isArray() || intermNode->getAsTyped()->getType().getArraySize() == 0) {
error(loc, "", fnCall->getName().c_str(), "array must be declared with a size before using this method");
length = 1;
} else
length = intermNode->getAsTyped()->getType().getArraySize();
TConstUnionArray unionArray(1);
unionArray[0].setIConst(length);
result = intermediate.addConstantUnion(unionArray, TType(EbtInt, EvqConst), loc);
} else if (op != EOpNull) {
//
// Then this should be a constructor.
// Don't go through the symbol table for constructors.
// Their parameters will be verified algorithmically.
//
TType type(EbtVoid); // use this to get the type back
if (! constructorError(loc, intermNode, *fnCall, op, type)) {
//
// It's a constructor, of type 'type'.
//
result = addConstructor(loc, intermNode, type, op);
if (result == 0)
error(loc, "cannot construct with these arguments", type.getCompleteString().c_str(), "");
}
} else {
//
// Find it in the symbol table.
//
const TFunction* fnCandidate;
bool builtIn;
fnCandidate = findFunction(loc, *fnCall, builtIn);
if (fnCandidate) {
// Error check for function requiring specific extensions present.
if (builtIn && fnCandidate->getNumExtensions())
requireExtensions(loc, fnCandidate->getNumExtensions(), fnCandidate->getExtensions(), fnCandidate->getName().c_str());
//
// A declared function. But, it might still map to a built-in
// operation.
//
op = fnCandidate->getBuiltInOp();
if (builtIn && op != EOpNull) {
// A function call mapped to a built-in operation.
result = intermediate.addBuiltInFunctionCall(loc, op, fnCandidate->getParamCount() == 1, intermNode, fnCandidate->getType());
if (result == 0) {
error(intermNode->getLoc(), " wrong operand type", "Internal Error",
"built in unary operator function. Type: %s",
static_cast<TIntermTyped*>(intermNode)->getCompleteString().c_str());
}
} else {
// This is a function call not mapped to built-in operation
result = intermediate.setAggregateOperator(intermAggregate, EOpFunctionCall, fnCandidate->getType(), loc);
result->getAsAggregate()->setName(fnCandidate->getMangledName());
// this is how we know whether the given function is a built-in function or a user-defined function
// if builtIn == false, it's a userDefined -> could be an overloaded built-in function also
// if builtIn == true, it's definitely a built-in function with EOpNull
if (! builtIn) {
result->getAsAggregate()->setUserDefined();
intermediate.addToCallGraph(infoSink, currentCaller, fnCandidate->getMangledName());
}
// Make sure storage qualifications work for these arguments.
TStorageQualifier qual;
TQualifierList& qualifierList = result->getAsAggregate()->getQualifierList();
for (int i = 0; i < fnCandidate->getParamCount(); ++i) {
qual = (*fnCandidate)[i].type->getQualifier().storage;
if (qual == EvqOut || qual == EvqInOut) {
if (lValueErrorCheck(result->getLoc(), "assign", result->getAsAggregate()->getSequence()[i]->getAsTyped()))
error(intermNode->getLoc(), "Constant value cannot be passed for 'out' or 'inout' parameters.", "Error", "");
}
qualifierList.push_back(qual);
}
if (builtIn)
nonOpBuiltInCheck(loc, *fnCandidate, *result->getAsAggregate());
}
}
}
// generic error recovery
// TODO: simplification: localize all the error recoveries that look like this, and taking type into account to reduce cascades
if (result == 0) {
TConstUnionArray unionArray(1);
unionArray[0].setDConst(0.0);
result = intermediate.addConstantUnion(unionArray, TType(EbtFloat, EvqConst), loc);
}
return result;
}
//
// Do additional checking of built-in function calls that were not mapped
// to built-in operations (e.g., texturing functions).
//
// Assumes there has been a semantically correct match to a built-in function.
//
void TParseContext::nonOpBuiltInCheck(TSourceLoc loc, const TFunction& fnCandidate, TIntermAggregate& callNode)
{
// built-in texturing functions get their return value precision from the precision of the sampler
if (fnCandidate.getType().getQualifier().precision == EpqNone &&
fnCandidate.getParamCount() > 0 && fnCandidate[0].type->getBasicType() == EbtSampler)
callNode.getQualifier().precision = callNode.getAsAggregate()->getSequence()[0]->getAsTyped()->getQualifier().precision;
if (fnCandidate.getName().compare(0, 7, "texture") == 0) {
if (fnCandidate.getName().compare(0, 13, "textureGather") == 0) {
TString featureString = fnCandidate.getName() + "(...)";
const char* feature = featureString.c_str();
requireProfile(loc, ~EEsProfile, feature);
int compArg = -1; // track which argument, if any, is the constant component argument
if (fnCandidate.getName().compare("textureGatherOffset") == 0) {
// GL_ARB_texture_gather is good enough for 2D non-shadow textures with no component argument
if (fnCandidate[0].type->getSampler().dim == Esd2D && ! fnCandidate[0].type->getSampler().shadow && fnCandidate.getParamCount() == 3)
profileRequires(loc, ~EEsProfile, 400, GL_ARB_texture_gather, feature);
else
profileRequires(loc, ~EEsProfile, 400, GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
} else if (fnCandidate.getName().compare("textureGatherOffsets") == 0) {
profileRequires(loc, ~EEsProfile, 400, GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 3;
// check for constant offsets
int offsetArg = fnCandidate[0].type->getSampler().shadow ? 3 : 2;
if (! callNode.getSequence()[offsetArg]->getAsConstantUnion())
error(loc, "must be a compile-time constant:", feature, "offsets argument");
} else if (fnCandidate.getName().compare("textureGather") == 0) {
// More than two arguments needs gpu_shader5, and rectangular or shadow needs gpu_shader5,
// otherwise, need GL_ARB_texture_gather.
if (fnCandidate.getParamCount() > 2 || fnCandidate[0].type->getSampler().dim == EsdRect || fnCandidate[0].type->getSampler().shadow) {
profileRequires(loc, ~EEsProfile, 400, GL_ARB_gpu_shader5, feature);
if (! fnCandidate[0].type->getSampler().shadow)
compArg = 2;
} else
profileRequires(loc, ~EEsProfile, 400, GL_ARB_texture_gather, feature);
}
if (compArg > 0 && compArg < fnCandidate.getParamCount()) {
if (callNode.getSequence()[compArg]->getAsConstantUnion()) {
int value = callNode.getSequence()[compArg]->getAsConstantUnion()->getConstArray()[0].getIConst();
if (value < 0 || value > 3)
error(loc, "must be 0, 1, 2, or 3:", feature, "component argument");
} else
error(loc, "must be a compile-time constant:", feature, "component argument");
}
} else {
// this is only for functions not starting "textureGather"...
if (fnCandidate.getName().find("Offset") != TString::npos) {
// Handle texture-offset limits checking
int arg = -1;
if (fnCandidate.getName().compare("textureOffset") == 0)
arg = 2;
else if (fnCandidate.getName().compare("texelFetchOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureProjOffset") == 0)
arg = 2;
else if (fnCandidate.getName().compare("textureLodOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureProjLodOffset") == 0)
arg = 3;
else if (fnCandidate.getName().compare("textureGradOffset") == 0)
arg = 4;
else if (fnCandidate.getName().compare("textureProjGradOffset") == 0)
arg = 4;
if (arg > 0) {
if (! callNode.getSequence()[arg]->getAsConstantUnion())
error(loc, "argument must be compile-time constant", "texel offset", "");
else {
const TType& type = callNode.getSequence()[arg]->getAsTyped()->getType();
for (int c = 0; c < type.getVectorSize(); ++c) {
int offset = callNode.getSequence()[arg]->getAsConstantUnion()->getConstArray()[c].getIConst();
if (offset > resources.maxProgramTexelOffset || offset < resources.minProgramTexelOffset)
error(loc, "value is out of range:", "texel offset", "[gl_MinProgramTexelOffset, gl_MaxProgramTexelOffset]");
}
}
}
}
}
}
}
//
// Handle seeing a built-in-type constructor call in the grammar.
//
TFunction* TParseContext::handleConstructorCall(TSourceLoc loc, TPublicType& publicType)
{
publicType.qualifier.precision = EpqNone;
TType type(publicType);
if (type.isArray()) {
profileRequires(loc, ENoProfile, 120, GL_3DL_array_objects, "arrayed constructor");
profileRequires(loc, EEsProfile, 300, 0, "arrayed constructor");
}
TOperator op = mapTypeToConstructorOp(type);
if (op == EOpNull) {
error(loc, "cannot construct this type", TType::getBasicString(publicType.basicType), "");
op = EOpConstructFloat;
publicType.basicType = EbtFloat;
TType errorType(publicType);
type.shallowCopy(errorType);
}
TString empty("");
return new TFunction(&empty, type, op);
}
//
// Given a type, find what operation would construct it.
//
TOperator TParseContext::mapTypeToConstructorOp(const TType& type)
{
if (type.isStruct())
return EOpConstructStruct;
TOperator op;
switch (type.getBasicType()) {
case EbtFloat:
if (type.isMatrix()) {
switch (type.getMatrixCols()) {
case 2:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructMat2x2; break;
case 3: op = EOpConstructMat2x3; break;
case 4: op = EOpConstructMat2x4; break;
default: break; // some compilers want this
}
break;
case 3:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructMat3x2; break;
case 3: op = EOpConstructMat3x3; break;
case 4: op = EOpConstructMat3x4; break;
default: break; // some compilers want this
}
break;
case 4:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructMat4x2; break;
case 3: op = EOpConstructMat4x3; break;
case 4: op = EOpConstructMat4x4; break;
default: break; // some compilers want this
}
break;
default: break; // some compilers want this
}
} else {
switch(type.getVectorSize()) {
case 1: op = EOpConstructFloat; break;
case 2: op = EOpConstructVec2; break;
case 3: op = EOpConstructVec3; break;
case 4: op = EOpConstructVec4; break;
default: break; // some compilers want this
}
}
break;
case EbtDouble:
if (type.getMatrixCols()) {
switch (type.getMatrixCols()) {
case 2:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructDMat2x2; break;
case 3: op = EOpConstructDMat2x3; break;
case 4: op = EOpConstructDMat2x4; break;
default: break; // some compilers want this
}
break;
case 3:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructDMat3x2; break;
case 3: op = EOpConstructDMat3x3; break;
case 4: op = EOpConstructDMat3x4; break;
default: break; // some compilers want this
}
break;
case 4:
switch (type.getMatrixRows()) {
case 2: op = EOpConstructDMat4x2; break;
case 3: op = EOpConstructDMat4x3; break;
case 4: op = EOpConstructDMat4x4; break;
default: break; // some compilers want this
}
break;
}
} else {
switch(type.getVectorSize()) {
case 1: op = EOpConstructDouble; break;
case 2: op = EOpConstructDVec2; break;
case 3: op = EOpConstructDVec3; break;
case 4: op = EOpConstructDVec4; break;
default: break; // some compilers want this
}
}
break;
case EbtInt:
switch(type.getVectorSize()) {
case 1: op = EOpConstructInt; break;
case 2: op = EOpConstructIVec2; break;
case 3: op = EOpConstructIVec3; break;
case 4: op = EOpConstructIVec4; break;
default: break; // some compilers want this
}
break;
case EbtUint:
switch(type.getVectorSize()) {
case 1: op = EOpConstructUint; break;
case 2: op = EOpConstructUVec2; break;
case 3: op = EOpConstructUVec3; break;
case 4: op = EOpConstructUVec4; break;
default: break; // some compilers want this
}
break;
case EbtBool:
switch(type.getVectorSize()) {
case 1: op = EOpConstructBool; break;
case 2: op = EOpConstructBVec2; break;
case 3: op = EOpConstructBVec3; break;
case 4: op = EOpConstructBVec4; break;
default: break; // some compilers want this
}
break;
default:
op = EOpNull;
break;
}
return op;
}
//
// Same error message for all places assignments don't work.
//
void TParseContext::assignError(TSourceLoc loc, const char* op, TString left, TString right)
{
error(loc, "", op, "cannot convert from '%s' to '%s'",
right.c_str(), left.c_str());
}
//
// Same error message for all places unary operations don't work.
//
void TParseContext::unaryOpError(TSourceLoc loc, const char* op, TString operand)
{
error(loc, " wrong operand type", op,
"no operation '%s' exists that takes an operand of type %s (or there is no acceptable conversion)",
op, operand.c_str());
}
//
// Same error message for all binary operations don't work.
//
void TParseContext::binaryOpError(TSourceLoc loc, const char* op, TString left, TString right)
{
error(loc, " wrong operand types:", op,
"no operation '%s' exists that takes a left-hand operand of type '%s' and "
"a right operand of type '%s' (or there is no acceptable conversion)",
op, left.c_str(), right.c_str());
}
//
// A basic type of EbtVoid is a key that the name string was seen in the source, but
// it was not found as a variable in the symbol table. If so, give the error
// message and insert a dummy variable in the symbol table to prevent future errors.
//
void TParseContext::variableCheck(TIntermTyped*& nodePtr)
{
TIntermSymbol* symbol = nodePtr->getAsSymbolNode();
if (! symbol)
return;
if (symbol->getType().getBasicType() == EbtVoid) {
error(symbol->getLoc(), "undeclared identifier", symbol->getName().c_str(), "");
// Add to symbol table to prevent future error messages on the same name
TVariable* fakeVariable = new TVariable(&symbol->getName(), TType(EbtFloat));
symbolTable.insert(*fakeVariable);
// substitute a symbol node for this new variable
nodePtr = intermediate.addSymbol(fakeVariable->getUniqueId(),
fakeVariable->getName(),
fakeVariable->getType(), symbol->getLoc());
} else {
switch (symbol->getQualifier().storage) {
case EvqPointCoord:
profileRequires(symbol->getLoc(), ENoProfile, 120, 0, "gl_PointCoord");
break;
default: break; // some compilers want this
}
}
}
//
// 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(TSourceLoc loc, const char* op, TIntermTyped* node)
{
TIntermSymbol* symNode = node->getAsSymbolNode();
TIntermBinary* binaryNode = node->getAsBinaryNode();
if (binaryNode) {
bool errorReturn;
switch(binaryNode->getOp()) {
case EOpIndexDirect:
case EOpIndexIndirect:
case EOpIndexDirectStruct:
return lValueErrorCheck(loc, op, binaryNode->getLeft());
case EOpVectorSwizzle:
errorReturn = lValueErrorCheck(loc, 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()->getConstArray()[0].getIConst();
offset[value]++;
if (offset[value] > 1) {
error(loc, " l-value of swizzle cannot have duplicate components", op, "", "");
return true;
}
}
}
return errorReturn;
default:
break;
}
error(loc, " l-value required", op, "", "");
return true;
}
const char* symbol = 0;
if (symNode != 0)
symbol = symNode->getName().c_str();
const char* message = 0;
switch (node->getQualifier().storage) {
case EvqConst: message = "can't modify a const"; break;
case EvqConstReadOnly: message = "can't modify a const"; break;
case EvqVaryingIn: message = "can't modify shader input"; break;
case EvqUniform: message = "can't modify a uniform"; break;
case EvqInstanceId: message = "can't modify gl_InstanceID"; break;
case EvqVertexId: message = "can't modify gl_VertexID"; break;
case EvqFace: message = "can't modify gl_FrontFace"; break;
case EvqFragCoord: message = "can't modify gl_FragCoord"; break;
case EvqPointCoord: message = "can't modify gl_PointCoord"; break;
default:
//
// Type that can't be written to?
//
switch (node->getBasicType()) {
case EbtSampler:
message = "can't modify a sampler";
break;
case EbtVoid:
message = "can't modify void";
break;
default:
break;
}
}
if (message == 0 && binaryNode == 0 && symNode == 0) {
error(loc, " l-value required", op, "", "");
return true;
}
//
// Everything else is okay, no error.
//
if (message == 0)
return false;
//
// If we get here, we have an error and a message.
//
if (symNode)
error(loc, " l-value required", op, "\"%s\" (%s)", symbol, message);
else
error(loc, " l-value required", op, "(%s)", message);
return true;
}
//
// Both test, and if necessary spit out an error, to see if the node is really
// a constant.
//
void TParseContext::constantValueCheck(TIntermTyped* node, const char* token)
{
if (node->getQualifier().storage != EvqConst)
error(node->getLoc(), "constant expression required", token, "");
}
//
// Both test, and if necessary spit out an error, to see if the node is really
// an integer.
//
void TParseContext::integerCheck(TIntermTyped* node, const char* token)
{
if ((node->getBasicType() == EbtInt || node->getBasicType() == EbtUint) && node->isScalar())
return;
error(node->getLoc(), "scalar integer expression required", token, "");
}
//
// Both test, and if necessary spit out an error, to see if we are currently
// globally scoped.
//
void TParseContext::globalCheck(TSourceLoc loc, const char* token)
{
if (! symbolTable.atGlobalLevel())
error(loc, "not allowed in nested scope", token, "");
}
//
// If it starts "gl_" or has double underscore, it's a reserved name.
// Except, if the symbol table is at a built-in level,
// which is when we are parsing built-ins.
//
void TParseContext::reservedErrorCheck(TSourceLoc loc, const TString& identifier)
{
if (! symbolTable.atBuiltInLevel()) {
if (builtInName(identifier))
error(loc, "reserved built-in name:", "gl_", identifier.c_str());
if (identifier.find("__") != TString::npos)
error(loc, "Two consecutive underscores are reserved for future use.", identifier.c_str(), "", "");
}
}
//
// Reserved errors for the preprocessor.
//
void TParseContext::reservedPpErrorCheck(TSourceLoc loc, const char* identifier, const char* op)
{
// "All macro names containing two consecutive underscores ( __ ) are reserved for future use as predefined
// macro names. All macro names prefixed with "GL_" ("GL" followed by a single underscore) are also
// reserved."
if (strncmp(identifier, "GL_", 3) == 0)
error(loc, "can't use with built-in names (\"GL_\" prefix)", op, "");
else if (strstr(identifier, "__") != 0)
error(loc, "can't use with built-in names (containing consecutive underscores)", op, "");
}
//
// See if this version/profile allows use of the line-continuation character '\'.
//
// Returns true if a line continuation should be done.
//
bool TParseContext::lineContinuationCheck(TSourceLoc loc, bool endOfComment)
{
const char* message = "line continuation";
bool lineContinuationAllowed = (profile == EEsProfile && version >= 300) ||
(profile != EEsProfile && version >= 420);
if (endOfComment) {
if (lineContinuationAllowed)
warn(loc, "used at end of comment; the following line is still part of the comment", message, "");
else
warn(loc, "used at end of comment, but this version does not provide line continuation", message, "");
return lineContinuationAllowed;
}
if (messages & EShMsgRelaxedErrors) {
warn(loc, "not allowed in this version", message, "");
} else {
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, message);
profileRequires(loc, EEsProfile, 300, 0, message);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, 0, message);
}
return lineContinuationAllowed;
}
bool TParseContext::builtInName(const TString& identifier)
{
return identifier.compare(0, 3, "gl_") == 0;
}
//
// 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::constructorError(TSourceLoc loc, TIntermNode* node, TFunction& function, TOperator op, TType& type)
{
type.shallowCopy(function.getType());
bool constructingMatrix = false;
switch(op) {
case EOpConstructMat2x2:
case EOpConstructMat2x3:
case EOpConstructMat2x4:
case EOpConstructMat3x2:
case EOpConstructMat3x3:
case EOpConstructMat3x4:
case EOpConstructMat4x2:
case EOpConstructMat4x3:
case EOpConstructMat4x4:
case EOpConstructDMat2x2:
case EOpConstructDMat2x3:
case EOpConstructDMat2x4:
case EOpConstructDMat3x2:
case EOpConstructDMat3x3:
case EOpConstructDMat3x4:
case EOpConstructDMat4x2:
case EOpConstructDMat4x3:
case EOpConstructDMat4x4:
constructingMatrix = true;
break;
default:
break;
}
//
// 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.
//
int size = 0;
bool constType = true;
bool full = false;
bool overFull = false;
bool matrixInMatrix = false;
bool arrayArg = false;
for (int i = 0; i < function.getParamCount(); ++i) {
size += function[i].type->getObjectSize();
if (constructingMatrix && function[i].type->isMatrix())
matrixInMatrix = true;
if (full)
overFull = true;
if (op != EOpConstructStruct && ! type.isArray() && size >= type.getObjectSize())
full = true;
if (function[i].type->getQualifier().storage != EvqConst)
constType = false;
if (function[i].type->isArray())
arrayArg = true;
}
if (constType)
type.getQualifier().storage = EvqConst;
if (type.isArray()) {
if (type.getArraySize() == 0) {
// auto adapt the constructor type to the number of arguments
type.changeArraySize(function.getParamCount());
} else if (type.getArraySize() != function.getParamCount()) {
error(loc, "array constructor needs one argument per array element", "constructor", "");
return true;
}
}
if (arrayArg && op != EOpConstructStruct) {
error(loc, "constructing from a non-dereferenced array", "constructor", "");
return true;
}
if (matrixInMatrix && ! type.isArray()) {
profileRequires(loc, ENoProfile, 120, 0, "constructing matrix from matrix");
return false;
}
if (overFull) {
error(loc, "too many arguments", "constructor", "");
return true;
}
if (op == EOpConstructStruct && ! type.isArray() && type.getStruct()->size() != function.getParamCount()) {
error(loc, "Number of constructor parameters does not match the number of structure fields", "constructor", "");
return true;
}
if ((op != EOpConstructStruct && size != 1 && size < type.getObjectSize()) ||
(op == EOpConstructStruct && size < type.getObjectSize())) {
error(loc, "not enough data provided for construction", "constructor", "");
return true;
}
TIntermTyped* typed = node->getAsTyped();
if (typed == 0) {
error(loc, "constructor argument does not have a type", "constructor", "");
return true;
}
if (op != EOpConstructStruct && typed->getBasicType() == EbtSampler) {
error(loc, "cannot convert a sampler", "constructor", "");
return true;
}
if (typed->getBasicType() == EbtVoid) {
error(loc, "cannot convert a void", "constructor", "");
return true;
}
return false;
}
// Checks to see if a void variable has been declared and raise an error message for such a case
//
// returns true in case of an error
//
bool TParseContext::voidErrorCheck(TSourceLoc loc, const TString& identifier, const TBasicType basicType)
{
if (basicType == EbtVoid) {
error(loc, "illegal use of type 'void'", identifier.c_str(), "");
return true;
}
return false;
}
// Checks to see if the node (for the expression) contains a scalar boolean expression or not
void TParseContext::boolCheck(TSourceLoc loc, const TIntermTyped* type)
{
if (type->getBasicType() != EbtBool || type->isArray() || type->isMatrix() || type->isVector())
error(loc, "boolean expression expected", "", "");
}
// This function checks to see if the node (for the expression) contains a scalar boolean expression or not
void TParseContext::boolCheck(TSourceLoc loc, const TPublicType& pType)
{
if (pType.basicType != EbtBool || pType.arraySizes || pType.matrixCols > 1 || (pType.vectorSize > 1))
error(loc, "boolean expression expected", "", "");
}
void TParseContext::samplerCheck(TSourceLoc loc, const TType& type, const TString& identifier)
{
if (type.getQualifier().storage == EvqUniform)
return;
if (type.getBasicType() == EbtStruct && containsSampler(type))
error(loc, "non-uniform struct contains a sampler or image:", type.getBasicTypeString().c_str(), identifier.c_str());
else if (type.getBasicType() == EbtSampler && type.getQualifier().storage != EvqUniform)
error(loc, "sampler/image types can only be used in uniform variables or function parameters:", type.getBasicTypeString().c_str(), identifier.c_str());
}
//
// move from parameter/unknown qualifiers to pipeline in/out qualifiers
//
void TParseContext::pipeInOutFix(TSourceLoc loc, TQualifier& qualifier)
{
switch (qualifier.storage) {
case EvqIn:
profileRequires(loc, ENoProfile, 130, 0, "in for stage inputs");
profileRequires(loc, EEsProfile, 300, 0, "in for stage inputs");
qualifier.storage = EvqVaryingIn;
break;
case EvqOut:
profileRequires(loc, ENoProfile, 130, 0, "out for stage outputs");
profileRequires(loc, EEsProfile, 300, 0, "out for stage outputs");
qualifier.storage = EvqVaryingOut;
break;
case EvqInOut:
qualifier.storage = EvqVaryingIn;
error(loc, "cannot use 'inout' at global scope", "", "");
break;
default:
break;
}
}
void TParseContext::globalQualifierCheck(TSourceLoc loc, const TQualifier& qualifier, const TPublicType& publicType)
{
if (! symbolTable.atGlobalLevel())
return;
if (qualifier.storage != EvqVaryingIn && qualifier.storage != EvqVaryingOut)
return;
// now, knowing it is a shader in/out, do all the in/out semantic checks
if (publicType.basicType == EbtBool) {
error(loc, "cannot be bool", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (language == EShLangVertex && qualifier.storage == EvqVaryingIn) {
if (publicType.basicType == EbtStruct) {
error(loc, "cannot be a structure or array", GetStorageQualifierString(qualifier.storage), "");
return;
}
if (publicType.arraySizes) {
requireProfile(loc, ~EEsProfile, "vertex input arrays");
profileRequires(loc, ENoProfile, 150, 0, "vertex input arrays");
}
}
if (language == EShLangFragment && qualifier.storage == EvqVaryingOut) {
profileRequires(loc, EEsProfile, 300, 0, "fragment shader output");
if (publicType.basicType == EbtStruct) {
error(loc, "cannot be a structure", GetStorageQualifierString(qualifier.storage), "");
return;
}
}
if (language == EShLangVertex && qualifier.storage == EvqVaryingOut) {
if (publicType.userDef) {
profileRequires(loc, EEsProfile, 300, 0, "vertex-shader struct output");
profileRequires(loc, ~EEsProfile, 150, 0, "vertex-shader struct output");
}
}
if (language == EShLangFragment && qualifier.storage == EvqVaryingIn) {
if (publicType.userDef) {
profileRequires(loc, EEsProfile, 300, 0, "fragment-shader struct input");
profileRequires(loc, ~EEsProfile, 150, 0, "fragment-shader struct input");
}
}
if (publicType.basicType == EbtInt || publicType.basicType == EbtUint || publicType.basicType == EbtDouble) {
profileRequires(loc, EEsProfile, 300, 0, "shader input/output");
if (! qualifier.flat) {
if (qualifier.storage == EvqVaryingIn && language == EShLangFragment)
error(loc, "must be qualified as flat", TType::getBasicString(publicType.basicType), GetStorageQualifierString(qualifier.storage));
else if (qualifier.storage == EvqVaryingOut && language == EShLangVertex && version == 300)
error(loc, "must be qualified as flat", TType::getBasicString(publicType.basicType), GetStorageQualifierString(qualifier.storage));
}
}
if (language == EShLangVertex && qualifier.storage == EvqVaryingIn &&
(qualifier.isAuxiliary() || qualifier.isInterpolation() || qualifier.isMemory() || qualifier.invariant))
error(loc, "vertex input cannot be further qualified", "", "");
}
//
// Merge characteristics of the 'src' qualifier into the 'dst'.
// If there is duplication, issue error messages, unless 'force'
// is specified, which means to just override default settings.
//
// Also, when force is false, it will be assumed that 'src' follows
// 'dst', for the purpose of error checking order for versions
// that require specific orderings of qualifiers.
//
void TParseContext::mergeQualifiers(TSourceLoc loc, TQualifier& dst, const TQualifier& src, bool force)
{
// Multiple auxiliary qualifiers (mostly done later by 'individual qualifiers')
if (src.isAuxiliary() && dst.isAuxiliary())
error(loc, "can only have one auxiliary qualifier (centroid, patch, and sample)", "", "");
// Multiple interpolation qualifiers (mostly done later by 'individual qualifiers')
if (src.isInterpolation() && dst.isInterpolation())
error(loc, "can only have one interpolation qualifier (flat, smooth, noperspective)", "", "");
// Ordering
if (! force && version < 420) {
// non-function parameters
if (src.invariant && (dst.isInterpolation() || dst.isAuxiliary() || dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "invariant qualifier must appear first", "", "");
else if (src.isInterpolation() && (dst.isAuxiliary() || dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "interpolation qualifiers must appear before storage and precision qualifiers", "", "");
else if (src.isAuxiliary() && (dst.storage != EvqTemporary || dst.precision != EpqNone))
error(loc, "Auxiliary qualifiers (centroid, patch, and sample) must appear before storage and precision qualifiers", "", "");
else if (src.storage != EvqTemporary && (dst.precision != EpqNone))
error(loc, "precision qualifier must appear as last qualifier", "", "");
// function parameters
if (src.storage == EvqConst && (dst.storage == EvqIn || dst.storage == EvqOut))
error(loc, "in/out must appear before const", "", "");
}
// Storage qualification
if (dst.storage == EvqTemporary || dst.storage == EvqGlobal)
dst.storage = src.storage;
else if ((dst.storage == EvqIn && src.storage == EvqOut) ||
(dst.storage == EvqOut && src.storage == EvqIn))
dst.storage = EvqInOut;
else if ((dst.storage == EvqIn && src.storage == EvqConst) ||
(dst.storage == EvqConst && src.storage == EvqIn))
dst.storage = EvqConstReadOnly;
else if (src.storage != EvqTemporary)
error(loc, "too many storage qualifiers", GetStorageQualifierString(src.storage), "");
// Precision qualifiers
if (! force && src.precision != EpqNone && dst.precision != EpqNone)
error(loc, "only one precision qualifier allowed", GetPrecisionQualifierString(src.precision), "");
if (dst.precision == EpqNone || (force && src.precision != EpqNone))
dst.precision = src.precision;
// Layout qualifiers
mergeObjectLayoutQualifiers(loc, dst, src);
// individual qualifiers
bool repeated = false;
#define MERGE_SINGLETON(field) repeated |= dst.field && src.field; dst.field |= src.field;
MERGE_SINGLETON(invariant);
MERGE_SINGLETON(centroid);
MERGE_SINGLETON(smooth);
MERGE_SINGLETON(flat);
MERGE_SINGLETON(nopersp);
MERGE_SINGLETON(patch);
MERGE_SINGLETON(sample);
MERGE_SINGLETON(shared);
MERGE_SINGLETON(coherent);
MERGE_SINGLETON(volatil);
MERGE_SINGLETON(restrict);
MERGE_SINGLETON(readonly);
MERGE_SINGLETON(writeonly);
if (repeated)
error(loc, "replicated qualifiers", "", "");
}
void TParseContext::setDefaultPrecision(TSourceLoc loc, TPublicType& publicType, TPrecisionQualifier qualifier)
{
TBasicType basicType = publicType.basicType;
if (basicType == EbtSampler) {
defaultSamplerPrecision[computeSamplerTypeIndex(publicType.sampler)] = qualifier;
return; // all is well
}
if (basicType == EbtInt || basicType == EbtFloat) {
if (publicType.isScalar()) {
defaultPrecision[basicType] = qualifier;
if (basicType == EbtInt)
defaultPrecision[EbtUint] = qualifier;
return; // all is well
}
}
error(loc, "cannot apply precision statement to this type; use 'float', 'int' or a sampler type", TType::getBasicString(basicType), "");
}
// used to flatten the sampler type space into a single dimension
// correlates with the declaration of defaultSamplerPrecision[]
int TParseContext::computeSamplerTypeIndex(TSampler& sampler)
{
int arrayIndex = sampler.arrayed ? 1 : 0;
int shadowIndex = sampler.shadow ? 1 : 0;
int externalIndex = sampler.external ? 1 : 0;
return EsdNumDims * (EbtNumTypes * (2 * (2 * arrayIndex + shadowIndex) + externalIndex) + sampler.type) + sampler.dim;
}
TPrecisionQualifier TParseContext::getDefaultPrecision(TPublicType& publicType)
{
if (publicType.basicType == EbtSampler)
return defaultSamplerPrecision[computeSamplerTypeIndex(publicType.sampler)];
else
return defaultPrecision[publicType.basicType];
}
void TParseContext::precisionQualifierCheck(TSourceLoc loc, TPublicType& publicType)
{
// Built-in symbols are allowed some ambiguous precisions, to be pinned down
// later by context.
if (profile != EEsProfile || parsingBuiltins)
return;
if (publicType.basicType == EbtFloat || publicType.basicType == EbtUint || publicType.basicType == EbtInt || publicType.basicType == EbtSampler) {
if (publicType.qualifier.precision == EpqNone) {
if (messages & EShMsgRelaxedErrors)
warn(loc, "type requires declaration of default precision qualifier", TType::getBasicString(publicType.basicType), "substituting 'mediump'");
else
error(loc, "type requires declaration of default precision qualifier", TType::getBasicString(publicType.basicType), "");
publicType.qualifier.precision = EpqMedium;
defaultPrecision[publicType.basicType] = EpqMedium;
}
} else if (publicType.qualifier.precision != EpqNone)
error(loc, "type cannot have precision qualifier", TType::getBasicString(publicType.basicType), "");
}
void TParseContext::parameterSamplerCheck(TSourceLoc loc, TStorageQualifier qualifier, const TType& type)
{
if ((qualifier == EvqOut || qualifier == EvqInOut) && type.getBasicType() != EbtStruct && type.getBasicType() == EbtSampler)
error(loc, "samplers cannot be output parameters", type.getBasicTypeString().c_str(), "");
}
bool TParseContext::containsSampler(const TType& type)
{
if (type.getBasicType() == EbtSampler)
return true;
if (type.getBasicType() == EbtStruct) {
TTypeList& structure = *type.getStruct();
for (unsigned int i = 0; i < structure.size(); ++i) {
if (containsSampler(*structure[i].type))
return true;
}
}
return false;
}
//
// Do size checking for an array type's size.
//
void TParseContext::arraySizeCheck(TSourceLoc loc, TIntermTyped* expr, int& size)
{
TIntermConstantUnion* constant = expr->getAsConstantUnion();
if (constant == 0 || (constant->getBasicType() != EbtInt && constant->getBasicType() != EbtUint)) {
error(loc, "array size must be a constant integer expression", "", "");
size = 1;
return;
}
size = constant->getConstArray()[0].getIConst();
if (size <= 0) {
error(loc, "array size must be a positive integer", "", "");
size = 1;
return;
}
}
//
// See if this qualifier can be an array.
//
// Returns true if there is an error.
//
bool TParseContext::arrayQualifierError(TSourceLoc loc, const TQualifier& qualifier)
{
if (qualifier.storage == EvqConst) {
profileRequires(loc, ENoProfile, 120, GL_3DL_array_objects, "const array");
profileRequires(loc, EEsProfile, 300, 0, "const array");
}
if (qualifier.storage == EvqVaryingIn && language == EShLangVertex) {
requireProfile(loc, ~EEsProfile, "vertex input arrays");
profileRequires(loc, ENoProfile, 150, 0, "vertex input arrays");
}
return false;
}
//
// Require array to have size
//
void TParseContext::arraySizeRequiredCheck(TSourceLoc loc, int size)
{
if (size == 0) {
error(loc, "array size required", "", "");
size = 1;
}
}
void TParseContext::arrayDimError(TSourceLoc loc)
{
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "arrays of arrays");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, 0, "arrays of arrays");
}
void TParseContext::arrayDimCheck(TSourceLoc loc, TArraySizes* sizes1, TArraySizes* sizes2)
{
if ((sizes1 && sizes2) ||
(sizes1 && sizes1->isArrayOfArrays()) ||
(sizes2 && sizes2->isArrayOfArrays()))
arrayDimError(loc);
}
void TParseContext::arrayDimCheck(TSourceLoc loc, const TType* type, TArraySizes* sizes2)
{
if ((type && type->isArray() && sizes2) ||
(sizes2 && sizes2->isArrayOfArrays()))
arrayDimError(loc);
}
//
// Do all the semantic checking for declaring an array, with and
// without a size, and make the right changes to the symbol table.
//
// size == 0 means no specified size.
//
void TParseContext::declareArray(TSourceLoc loc, TString& identifier, const TType& type, TSymbol*& symbol, bool& newDeclaration)
{
if (! symbol) {
bool currentScope;
symbol = symbolTable.find(identifier, 0, &currentScope);
if (symbol == 0 || ! currentScope) {
//
// Successfully process a new definition.
// (Redeclarations have to take place at the same scope; otherwise they are hiding declarations)
//
symbol = new TVariable(&identifier, type);
symbolTable.insert(*symbol);
newDeclaration = true;
// Handle user geometry shader input arrays: see inputArrayNodeResizeList comment in ParseHelper.h
if (language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn && ! symbolTable.atBuiltInLevel()) {
inputArraySymbolResizeList.push_back(symbol);
checkInputArrayConsistency(loc, true);
}
return;
}
if (symbol->getAsAnonMember()) {
error(loc, "cannot redeclare a user-block member array", identifier.c_str(), "");
return;
}
}
//
// Process a redeclaration.
//
if (! symbol) {
error(loc, "array variable name expected", identifier.c_str(), "");
return;
}
TType& newType = symbol->getWritableType();
if (! newType.isArray()) {
error(loc, "redeclaring non-array as array", identifier.c_str(), "");
return;
}
if (newType.getArraySize() > 0) {
// be more leniant for input arrays to geometry shaders, where the redeclaration is the same size
if (! (language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn && newType.getArraySize() == type.getArraySize()))
error(loc, "redeclaration of array with size", identifier.c_str(), "");
return;
}
if (! newType.sameElementType(type)) {
error(loc, "redeclaration of array with a different type", identifier.c_str(), "");
return;
}
arrayLimitCheck(loc, identifier, type.getArraySize());
newType.shareArraySizes(type);
if (language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn)
checkInputArrayConsistency(loc);
}
void TParseContext::updateMaxArraySize(TSourceLoc loc, TIntermNode *node, int index)
{
// maybe there is nothing to do...
TIntermTyped* typedNode = node->getAsTyped();
if (typedNode->getType().getMaxArraySize() > index)
return;
// something to do...
// TODO: 1.50 linker: unsized block member array: 'node' could be an expression for a dereference
TIntermSymbol* symbolNode = node->getAsSymbolNode();
if (! symbolNode)
return;
TSymbol* symbol = symbolTable.find(symbolNode->getName());
assert(symbol);
if (symbol == 0)
return;
if (symbol->getAsFunction()) {
error(loc, "array variable name expected", symbolNode->getName().c_str(), "");
return;
}
// For read-only built-ins, add a new variable for holding the maximum array size of an implicitly-sized shared array.
// TODO: desktop linker: unsized arrays: is this new array type shared with the node?
if (symbol->isReadOnly()) {
symbol = symbolTable.copyUp(symbol);
// Handle geometry shader input arrays: see inputArrayNodeResizeList comment in ParseHelper.h
if (language == EShLangGeometry && symbol->getType().getQualifier().storage == EvqVaryingIn)
inputArraySymbolResizeList.push_back(symbol);
// Save it in the AST for linker use.
intermediate.addSymbolLinkageNode(linkage, *symbol);
}
symbol->getWritableType().setMaxArraySize(index + 1);
}
//
// Enforce non-initializer type/qualifier rules.
//
void TParseContext::nonInitConstCheck(TSourceLoc loc, TString& identifier, TType& type)
{
//
// Make the qualifier make sense, given that there is an initializer.
//
if (type.getQualifier().storage == EvqConst ||
type.getQualifier().storage == EvqConstReadOnly) {
type.getQualifier().storage = EvqTemporary;
error(loc, "variables with qualifier 'const' must be initialized", identifier.c_str(), "");
}
}
//
// See if the identifier is a built-in symbol that can be redeclared, and if so,
// copy the symbol table's read-only built-in variable to the current
// global level, where it can be modified based on the passed in type.
//
// Returns 0 if no redeclaration took place; meaning a normal declaration still
// needs to occur for it, not necessarily an error.
//
// Returns a redeclared and type-modified variable if a redeclarated occurred.
//
TSymbol* TParseContext::redeclareBuiltinVariable(TSourceLoc loc, const TString& identifier, const TQualifier& qualifier, const TShaderQualifiers& publicType, bool& newDeclaration)
{
if (profile == EEsProfile || ! builtInName(identifier) || symbolTable.atBuiltInLevel() || ! symbolTable.atGlobalLevel())
return 0;
// Potentially redeclaring a built-in variable...
if ((identifier == "gl_FragDepth" && version >= 420) ||
(identifier == "gl_FragCoord" && version >= 150) ||
(identifier == "gl_ClipDistance" && version >= 130) ||
(identifier == "gl_FrontColor" && version >= 130) ||
(identifier == "gl_BackColor" && version >= 130) ||
(identifier == "gl_FrontSecondaryColor" && version >= 130) ||
(identifier == "gl_BackSecondaryColor" && version >= 130) ||
(identifier == "gl_SecondaryColor" && version >= 130) ||
(identifier == "gl_Color" && version >= 130 && language == EShLangFragment) ||
identifier == "gl_TexCoord") {
// Find the existing symbol, if any.
bool builtIn;
TSymbol* symbol = symbolTable.find(identifier, &builtIn);
// If the symbol was not found, this must be a version/profile/stage
// that doesn't have it.
if (! symbol)
return 0;
// If it wasn't at a built-in level, then it's already been redeclared;
// that is, this is a redeclaration of a redeclaration, reuse that initial
// redeclaration. Otherwise, make the new one.
if (builtIn) {
// Copy the symbol up to make a writable version
newDeclaration = true;
symbol = symbolTable.copyUp(symbol);
// Handle geometry shader input arrays: see inputArrayNodeResizeList comment in ParseHelper.h
if (language == EShLangGeometry && symbol->getType().getQualifier().storage == EvqVaryingIn && symbol->getType().isArray())
inputArraySymbolResizeList.push_back(symbol);
// Save it in the AST for linker use.
intermediate.addSymbolLinkageNode(linkage, *symbol);
}
// Now, modify the type of the copy, as per the type of the current redeclaration.
TQualifier& symbolQualifier = symbol->getWritableType().getQualifier();
if (identifier == "gl_FrontColor" ||
identifier == "gl_BackColor" ||
identifier == "gl_FrontSecondaryColor" ||
identifier == "gl_BackSecondaryColor" ||
identifier == "gl_SecondaryColor" ||
identifier == "gl_Color") {
symbolQualifier.flat = qualifier.flat;
symbolQualifier.smooth = qualifier.smooth;
symbolQualifier.nopersp = qualifier.nopersp;
if (qualifier.hasLayout())
error(loc, "cannot apply layout qualifier to", "redeclaration", symbol->getName().c_str());
if (qualifier.isMemory() || qualifier.isAuxiliary() || symbol->getType().getQualifier().storage != qualifier.storage)
error(loc, "cannot change storage, memory, or auxiliary qualification of", "redeclaration", symbol->getName().c_str());
} else if (identifier == "gl_TexCoord" ||
identifier == "gl_ClipDistance") {
if (qualifier.hasLayout() || qualifier.isMemory() || qualifier.isAuxiliary() ||
qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
symbolQualifier.storage != qualifier.storage)
error(loc, "cannot change qualification of", "redeclaration", symbol->getName().c_str());
} else if (identifier == "gl_FragCoord") {
if (intermediate.inIoAccessed("gl_FragCoord"))
error(loc, "cannot redeclare after use", "gl_FragCoord", "");
if (qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
qualifier.isMemory() || qualifier.isAuxiliary())
error(loc, "can only change layout qualification of", "redeclaration", symbol->getName().c_str());
if (identifier == "gl_FragCoord" && qualifier.storage != EvqVaryingIn)
error(loc, "cannot change input storage qualification of", "redeclaration", symbol->getName().c_str());
if (! builtIn && (publicType.pixelCenterInteger != intermediate.getPixelCenterInteger() ||
publicType.originUpperLeft != intermediate.getOriginUpperLeft()))
error(loc, "cannot redeclare with different qualification:", "redeclaration", symbol->getName().c_str());
if (publicType.pixelCenterInteger)
intermediate.setPixelCenterInteger();
if (publicType.originUpperLeft)
intermediate.setOriginUpperLeft();
} else if (identifier == "gl_FragDepth") {
if (qualifier.nopersp != symbolQualifier.nopersp || qualifier.flat != symbolQualifier.flat ||
qualifier.isMemory() || qualifier.isAuxiliary())
error(loc, "can only change layout qualification of", "redeclaration", symbol->getName().c_str());
if (qualifier.storage != EvqVaryingOut)
error(loc, "cannot change output storage qualification of", "redeclaration", symbol->getName().c_str());
// TODO 4.2: gl_FragDepth redeclaration
}
// TODO: semantics quality: separate smooth from nothing declared, then use IsInterpolation for several tests above
return symbol;
}
return 0;
}
//
// Either redeclare the requested block, or give an error message why it can't be done.
//
void TParseContext::redeclareBuiltinBlock(TSourceLoc loc, TTypeList& newTypeList, const TString& blockName, const TString* instanceName, TArraySizes* arraySizes)
{
const char* feature = "built-in block redeclaration";
requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 410, GL_ARB_separate_shader_objects, feature);
if (blockName != "gl_PerVertex" && blockName != "gl_PerFragment") {
error(loc, "cannot redeclare block: ", "block declaration", blockName.c_str());
return;
}
// Redeclaring a built-in block...
if (instanceName && ! builtInName(*instanceName)) {
error(loc, "cannot redeclare a built-in block with a user name", instanceName->c_str(), "");
return;
}
// Blocks with instance names are easy to find, lookup the instance name,
// Anonymous blocks need to be found via a member.
bool builtIn;
TSymbol* block;
if (instanceName)
block = symbolTable.find(*instanceName, &builtIn);
else
block = symbolTable.find(newTypeList.front().type->getFieldName(), &builtIn);
// If the block was not found, this must be a version/profile/stage
// that doesn't have it, or the instance name is wrong.
const char* errorName = instanceName ? instanceName->c_str() : newTypeList.front().type->getFieldName().c_str();
if (! block) {
error(loc, "no declaration found for redeclaration", errorName, "");
return;
}
// Built-in blocks cannot be redeclared more than once, which if happened,
// we'd be finding the already redeclared one here, rather than the built in.
if (! builtIn) {
error(loc, "can only redeclare a built-in block once, and before any use", blockName.c_str(), "");
return;
}
// Copy the block to make a writable version, to insert into the block table after editing.
block = symbolTable.copyUpDeferredInsert(block);
if (block->getType().getBasicType() != EbtBlock) {
error(loc, "cannot redeclare a non block as a block", errorName, "");
return;
}
// Handle geometry shader input arrays: see inputArrayNodeResizeList comment in ParseHelper.h
if (language == EShLangGeometry && block->getType().isArray() && block->getType().getQualifier().storage == EvqVaryingIn)
inputArraySymbolResizeList.push_back(block);
// Edit and error check the container against the redeclaration
// - remove unused members
// - ensure remaining qualifiers/types match
TType& type = block->getWritableType();
TTypeList::iterator member = type.getStruct()->begin();
size_t numOriginalMembersFound = 0;
while (member != type.getStruct()->end()) {
// look for match
bool found = false;
TTypeList::iterator newMember;
TSourceLoc memberLoc;
for (newMember = newTypeList.begin(); newMember != newTypeList.end(); ++newMember) {
if (member->type->getFieldName() == newMember->type->getFieldName()) {
found = true;
memberLoc = newMember->loc;
break;
}
}
if (found) {
++numOriginalMembersFound;
// - ensure match between redeclared members' types
// - check for things that can't be changed
// - update things that can be changed
TType& oldType = *member->type;
const TType& newType = *newMember->type;
if (! newType.sameElementType(oldType))
error(memberLoc, "cannot redeclare block member with a different type", member->type->getFieldName().c_str(), "");
if (oldType.isArray() != newType.isArray())
error(memberLoc, "cannot change arrayness of redeclared block member", member->type->getFieldName().c_str(), "");
else if (! oldType.sameArrayness(newType) && oldType.getArraySize() > 0)
error(memberLoc, "cannot change array size of redeclared block member", member->type->getFieldName().c_str(), "");
else if (newType.isArray())
arrayLimitCheck(loc, member->type->getFieldName(), newType.getArraySize());
if (newType.getQualifier().isMemory())
error(memberLoc, "cannot add memory qualifier to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().hasLayout())
error(memberLoc, "cannot add layout to redeclared block member", member->type->getFieldName().c_str(), "");
if (newType.getQualifier().patch)
error(memberLoc, "cannot add patch to redeclared block member", member->type->getFieldName().c_str(), "");
oldType.getQualifier().centroid = newType.getQualifier().centroid;
oldType.getQualifier().sample = newType.getQualifier().sample;
oldType.getQualifier().invariant = newType.getQualifier().invariant;
oldType.getQualifier().smooth = newType.getQualifier().smooth;
oldType.getQualifier().flat = newType.getQualifier().flat;
oldType.getQualifier().nopersp = newType.getQualifier().nopersp;
// go to next member
++member;
} else {
// Use EbtVoid to tag missing members of anonymous blocks that have been redeclared,
// to hide the original (shared) declaration.
// (Instance-named blocks can just have the member removed.)
if (instanceName)
member = type.getStruct()->erase(member);
else {
member->type->setElementType(EbtVoid, 1, 0, 0, 0);
++member;
}
}
}
if (numOriginalMembersFound < newTypeList.size())
error(loc, "block redeclaration has extra members", blockName.c_str(), "");
if (type.isArray() != (arraySizes != 0))
error(loc, "cannot change arrayness of redeclared block", blockName.c_str(), "");
else if (type.isArray() && type.getArraySize() > 0 && type.getArraySize() != arraySizes->getSize())
error(loc, "cannot change array size of redeclared block", blockName.c_str(), "");
symbolTable.insert(*block);
// Check for general layout qualifier errors
layoutTypeCheck(loc, *block);
// Save it in the AST for linker use.
intermediate.addSymbolLinkageNode(linkage, *block);
}
void TParseContext::paramCheckFix(TSourceLoc loc, const TStorageQualifier& qualifier, TType& type)
{
switch (qualifier) {
case EvqConst:
case EvqConstReadOnly:
type.getQualifier().storage = EvqConstReadOnly;
break;
case EvqIn:
case EvqOut:
case EvqInOut:
type.getQualifier().storage = qualifier;
break;
case EvqTemporary:
type.getQualifier().storage = EvqIn;
break;
default:
type.getQualifier().storage = EvqIn;
error(loc, "storage qualifier not allowed on function parameter", GetStorageQualifierString(qualifier), "");
break;
}
}
void TParseContext::paramCheckFix(TSourceLoc loc, const TQualifier& qualifier, TType& type)
{
if (qualifier.isAuxiliary() ||
qualifier.isInterpolation())
error(loc, "cannot use auxiliary or interpolation qualifiers on a function parameter", "", "");
if (qualifier.hasLayout())
error(loc, "cannot use layout qualifiers on a function parameter", "", "");
if (qualifier.invariant)
error(loc, "cannot use invariant qualifier on a function parameter", "", "");
paramCheckFix(loc, qualifier.storage, type);
}
void TParseContext::nestedBlockCheck(TSourceLoc loc)
{
if (structNestingLevel > 0)
error(loc, "cannot nest a block definition inside a structure or block", "", "");
++structNestingLevel;
}
void TParseContext::nestedStructCheck(TSourceLoc loc)
{
if (structNestingLevel > 0)
error(loc, "cannot nest a structure definition inside a structure or block", "", "");
++structNestingLevel;
}
void TParseContext::arrayObjectCheck(TSourceLoc loc, const TType& type, const char* op)
{
// Some versions don't allow comparing arrays or structures containing arrays
if (type.containsArray()) {
profileRequires(loc, ENoProfile, 120, GL_3DL_array_objects, op);
profileRequires(loc, EEsProfile, 300, 0, op);
}
}
void TParseContext::opaqueCheck(TSourceLoc loc, const TType& type, const char* op)
{
if (containsSampler(type))
error(loc, "can't use with samplers or structs containing samplers", op, "");
}
void TParseContext::structTypeCheck(TSourceLoc loc, TPublicType& publicType)
{
TTypeList& typeList = *publicType.userDef->getStruct();
// fix and check for member storage qualifiers and types that don't belong within a structure
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
TSourceLoc memberLoc = typeList[member].loc;
if (memberQualifier.isAuxiliary() ||
memberQualifier.isInterpolation() ||
(memberQualifier.storage != EvqTemporary && memberQualifier.storage != EvqGlobal))
error(memberLoc, "cannot use storage or interpolation qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.isMemory())
error(memberLoc, "cannot use memory qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.hasLayout())
error(memberLoc, "cannot use layout qualifiers on structure members", typeList[member].type->getFieldName().c_str(), "");
if (memberQualifier.invariant)
error(memberLoc, "cannot use invariant qualifier on structure members", typeList[member].type->getFieldName().c_str(), "");
}
}
//
// See if this loop satisfies the limitations for ES 2.0 (version 100) for loops in Appendex A:
//
// "The loop index has type int or float.
//
// "The for statement has the form:
// for ( init-declaration ; condition ; expression )
// init-declaration has the form: type-specifier identifier = constant-expression
// condition has the form: loop-index relational_operator constant-expression
// where relational_operator is one of: > >= < <= == or !=
// expression [sic] has one of the following forms:
// loop-index++
// loop-index--
// loop-index += constant-expression
// loop-index -= constant-expression
//
// The body is handled in an AST traversal.
//
void TParseContext::inductiveLoopCheck(TSourceLoc loc, TIntermNode* init, TIntermLoop* loop)
{
// loop index init must exist and be a declaration, which shows up in the AST as an aggregate of size 1 of the declaration
bool badInit = false;
if (! init || ! init->getAsAggregate() || ! init->getAsAggregate()->getSequence().size() == 1)
badInit = true;
TIntermBinary* binaryInit;
if (! badInit) {
// get the declaration assignment
binaryInit = init->getAsAggregate()->getSequence()[0]->getAsBinaryNode();
if (! binaryInit)
badInit = true;
}
if (badInit) {
error(loc, "inductive-loop init-declaration requires the form \"type-specifier loop-index = constant-expression\"", "limitations", "");
return;
}
// loop index must be type int or float
if (! binaryInit->getType().isScalar() || (binaryInit->getBasicType() != EbtInt && binaryInit->getBasicType() != EbtFloat)) {
error(loc, "inductive loop requires a scalar 'int' or 'float' loop index", "limitations", "");
return;
}
// init is the form "loop-index = constant"
if (binaryInit->getOp() != EOpAssign || ! binaryInit->getLeft()->getAsSymbolNode() || ! binaryInit->getRight()->getAsConstantUnion()) {
error(loc, "inductive-loop init-declaration requires the form \"type-specifier loop-index = constant-expression\"", "limitations", "");
return;
}
// get the unique id of the loop index
int loopIndex = binaryInit->getLeft()->getAsSymbolNode()->getId();
inductiveLoopIds.insert(loopIndex);
// condition's form must be "loop-index relational-operator constant-expression"
bool badCond = ! loop->getTest();
if (! badCond) {
TIntermBinary* binaryCond = loop->getTest()->getAsBinaryNode();
badCond = ! binaryCond;
if (! badCond) {
switch (binaryCond->getOp()) {
case EOpGreaterThan:
case EOpGreaterThanEqual:
case EOpLessThan:
case EOpLessThanEqual:
case EOpEqual:
case EOpNotEqual:
break;
default:
badCond = true;
}
}
if (binaryCond && (! binaryCond->getLeft()->getAsSymbolNode() ||
binaryCond->getLeft()->getAsSymbolNode()->getId() != loopIndex ||
! binaryCond->getRight()->getAsConstantUnion()))
badCond = true;
}
if (badCond) {
error(loc, "inductive-loop condition requires the form \"loop-index <comparison-op> constant-expression\"", "limitations", "");
return;
}
// loop-index++
// loop-index--
// loop-index += constant-expression
// loop-index -= constant-expression
bool badTerminal = ! loop->getTerminal();
if (! badTerminal) {
TIntermUnary* unaryTerminal = loop->getTerminal()->getAsUnaryNode();
TIntermBinary* binaryTerminal = loop->getTerminal()->getAsBinaryNode();
if (unaryTerminal || binaryTerminal) {
switch(loop->getTerminal()->getAsOperator()->getOp()) {
case EOpPostDecrement:
case EOpPostIncrement:
case EOpAddAssign:
case EOpSubAssign:
break;
default:
badTerminal = true;
}
} else
badTerminal = true;
if (binaryTerminal && (! binaryTerminal->getLeft()->getAsSymbolNode() ||
binaryTerminal->getLeft()->getAsSymbolNode()->getId() != loopIndex ||
! binaryTerminal->getRight()->getAsConstantUnion()))
badTerminal = true;
if (unaryTerminal && (! unaryTerminal->getOperand()->getAsSymbolNode() ||
unaryTerminal->getOperand()->getAsSymbolNode()->getId() != loopIndex))
badTerminal = true;
}
if (badTerminal) {
error(loc, "inductive-loop termination requires the form \"loop-index++, loop-index--, loop-index += constant-expression, or loop-index -= constant-expression\"", "limitations", "");
return;
}
// the body
inductiveLoopBodyCheck(loop->getBody(), loopIndex, symbolTable);
}
// Do limit checks against for all built-in arrays.
void TParseContext::arrayLimitCheck(TSourceLoc loc, const TString& identifier, int size)
{
if (identifier.compare("gl_TexCoord") == 0)
limitCheck(loc, size, "gl_MaxTextureCoords", "gl_TexCoord array size");
else if (identifier.compare("gl_ClipDistance") == 0)
limitCheck(loc, size, "gl_MaxClipDistances", "gl_ClipDistance array size");
}
// See if the provide value is less than the symbol indicated by limit,
// which should be a constant in the symbol table.
void TParseContext::limitCheck(TSourceLoc loc, int value, const char* limit, const char* feature)
{
TSymbol* symbol = symbolTable.find(limit);
assert(symbol->getAsVariable());
const TConstUnionArray& constArray = symbol->getAsVariable()->getConstArray();
assert(! constArray.empty());
if (value >= constArray[0].getIConst())
error(loc, "must be less than", feature, "%s (%d)", limit, constArray[0].getIConst());
}
//
// Do any additional error checking, etc., once we know the parsing is done.
//
void TParseContext::finalErrorCheck()
{
// Check on array indexes for ES 2.0 (version 100) limitations.
for (size_t i = 0; i < needsIndexLimitationChecking.size(); ++i)
constantIndexExpressionCheck(needsIndexLimitationChecking[i]);
}
//
// Layout qualifier stuff.
//
// Put the id's layout qualification into the public type. This is before we know any
// type information for error checking.
void TParseContext::setLayoutQualifier(TSourceLoc loc, TPublicType& publicType, TString& id)
{
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
if (id == TQualifier::getLayoutMatrixString(ElmColumnMajor)) {
publicType.qualifier.layoutMatrix = ElmColumnMajor;
return;
}
if (id == TQualifier::getLayoutMatrixString(ElmRowMajor)) {
publicType.qualifier.layoutMatrix = ElmRowMajor;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpPacked)) {
publicType.qualifier.layoutPacking = ElpPacked;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpShared)) {
publicType.qualifier.layoutPacking = ElpShared;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpStd140)) {
publicType.qualifier.layoutPacking = ElpStd140;
return;
}
if (id == TQualifier::getLayoutPackingString(ElpStd430)) {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "std430");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, 0, "std430");
publicType.qualifier.layoutPacking = ElpStd430;
return;
}
if (language == EShLangGeometry || language == EShLangTessEvaluation) {
if (id == TQualifier::getGeometryString(ElgTriangles)) {
publicType.shaderQualifiers.geometry = ElgTriangles;
return;
}
if (language == EShLangGeometry) {
if (id == TQualifier::getGeometryString(ElgPoints)) {
publicType.shaderQualifiers.geometry = ElgPoints;
return;
}
if (id == TQualifier::getGeometryString(ElgLineStrip)) {
publicType.shaderQualifiers.geometry = ElgLineStrip;
return;
}
if (id == TQualifier::getGeometryString(ElgLines)) {
publicType.shaderQualifiers.geometry = ElgLines;
return;
}
if (id == TQualifier::getGeometryString(ElgLinesAdjacency)) {
publicType.shaderQualifiers.geometry = ElgLinesAdjacency;
return;
}
if (id == TQualifier::getGeometryString(ElgTrianglesAdjacency)) {
publicType.shaderQualifiers.geometry = ElgTrianglesAdjacency;
return;
}
if (id == TQualifier::getGeometryString(ElgTriangleStrip)) {
publicType.shaderQualifiers.geometry = ElgTriangleStrip;
return;
}
} else {
// tessellation evaluation
// TODO: tessellation: semantic check these are on the in qualifier only
// input primitive
if (id == TQualifier::getGeometryString(ElgTriangles)) {
publicType.shaderQualifiers.geometry = ElgTriangles;
return;
}
if (id == TQualifier::getGeometryString(ElgQuads)) {
publicType.shaderQualifiers.geometry = ElgQuads;
return;
}
if (id == TQualifier::getGeometryString(ElgIsolines)) {
publicType.shaderQualifiers.geometry = ElgIsolines;
return;
}
// vertex spacing
if (id == TQualifier::getVertexSpacingString(EvsEqual)) {
publicType.shaderQualifiers.spacing = EvsEqual;
return;
}
if (id == TQualifier::getVertexSpacingString(EvsFractionalEven)) {
publicType.shaderQualifiers.spacing = EvsFractionalEven;
return;
}
if (id == TQualifier::getVertexSpacingString(EvsFractionalOdd)) {
publicType.shaderQualifiers.spacing = EvsFractionalOdd;
return;
}
// triangle order
if (id == TQualifier::getVertexOrderString(EvoCw)) {
publicType.shaderQualifiers.order = EvoCw;
return;
}
if (id == TQualifier::getVertexOrderString(EvoCcw)) {
publicType.shaderQualifiers.order = EvoCcw;
return;
}
// point mode
if (id == "point_mode") {
publicType.shaderQualifiers.pointMode = true;
return;
}
}
}
if (language == EShLangFragment) {
if (id == "origin_upper_left") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "origin_upper_left");
publicType.shaderQualifiers.originUpperLeft = true;
return;
}
if (id == "pixel_center_integer") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "pixel_center_integer");
publicType.shaderQualifiers.pixelCenterInteger = true;
return;
}
}
error(loc, "unrecognized layout identifier, or qualifier requires assignemnt (e.g., binding = 4)", id.c_str(), "");
}
// Put the id's layout qualifier value into the public type. This is before we know any
// type information for error checking.
void TParseContext::setLayoutQualifier(TSourceLoc loc, TPublicType& publicType, TString& id, int value)
{
if (value < 0) {
error(loc, "cannot be negative", "layout qualifier value", "");
return;
// TODO: 4.4: test the above, once expressions are allowed; until then, can't even express a negative location
}
std::transform(id.begin(), id.end(), id.begin(), ::tolower);
if (id == "location") {
requireProfile(loc, EEsProfile | ECoreProfile | ECompatibilityProfile, "location");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 330, 0, "location");
if ((unsigned int)value >= TQualifier::layoutLocationEnd)
error(loc, "location is too large", id.c_str(), "");
else
publicType.qualifier.layoutSlotLocation = value;
return;
}
if (id == "binding") {
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "binding");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, GL_ARB_shading_language_420pack, "binding");
if ((unsigned int)value >= TQualifier::layoutBindingEnd)
error(loc, "binding is too large", id.c_str(), "");
else
publicType.qualifier.layoutBinding = value;
return;
}
switch (language) {
case EShLangVertex:
break;
case EShLangTessControl:
if (id == "vertices") {
// TODO: tessellation: implement gl_out[] array sizing based on this
// TODO: tessellation: semantic check this is on the out qualifier only
publicType.shaderQualifiers.vertices = value;
return;
}
break;
case EShLangTessEvaluation:
break;
case EShLangGeometry:
if (id == "invocations") {
profileRequires(loc, ECompatibilityProfile | ECoreProfile, 400, 0, "invocations");
publicType.shaderQualifiers.invocations = value;
return;
}
if (id == "max_vertices") {
publicType.shaderQualifiers.vertices = value;
if (value > resources.maxGeometryOutputVertices)
error(loc, "too large, must be less than gl_MaxGeometryOutputVertices", "max_vertices", "");
return;
}
if (id == "stream") {
publicType.qualifier.layoutStream = value;
return;
}
break;
case EShLangFragment:
break;
case EShLangCompute:
break;
default:
break;
}
error(loc, "there is no such layout identifier for this stage taking an assigned value", id.c_str(), "");
}
//
// Merge characteristics of the 'src' qualifier into the 'dst', at the TPublicType level,
// which means for layout-qualifier information not kept per qualifier.
//
void TParseContext::mergeShaderLayoutQualifiers(TSourceLoc loc, TShaderQualifiers& dst, const TShaderQualifiers& src)
{
if (src.geometry != ElgNone)
dst.geometry = src.geometry;
if (src.invocations != 0)
dst.invocations = src.invocations;
if (src.vertices != 0)
dst.vertices = src.vertices;
if (src.pixelCenterInteger)
dst.pixelCenterInteger = src.pixelCenterInteger;
if (src.originUpperLeft)
dst.originUpperLeft = src.originUpperLeft;
}
// Merge any layout qualifier information from src into dst, leaving everything else in dst alone
void TParseContext::mergeObjectLayoutQualifiers(TSourceLoc loc, TQualifier& dst, const TQualifier& src)
{
if (src.layoutMatrix != ElmNone)
dst.layoutMatrix = src.layoutMatrix;
if (src.layoutPacking != ElpNone)
dst.layoutPacking = src.layoutPacking;
if (src.hasLocation())
dst.layoutSlotLocation = src.layoutSlotLocation;
if (src.hasBinding())
dst.layoutBinding = src.layoutBinding;
if (src.hasStream())
dst.layoutStream = src.layoutStream;
}
// Do error layout error checking given a full variable/block declaration.
void TParseContext::layoutTypeCheck(TSourceLoc loc, const TSymbol& symbol)
{
const TType& type = symbol.getType();
const TQualifier& qualifier = type.getQualifier();
// first, qualifier only error checking
layoutQualifierCheck(loc, qualifier);
// now, error checking combining type and qualifier
if (qualifier.hasLocation()) {
switch (qualifier.storage) {
case EvqVaryingIn:
{
if (type.getBasicType() == EbtBlock)
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, 0 /* TODO ARB_enhanced_layouts*/, "location qualifier on input block");
break;
}
case EvqVaryingOut:
{
if (type.getBasicType() == EbtBlock)
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 440, 0 /* TODO ARB_enhanced_layouts*/, "location qualifier on output block");
break;
}
case EvqUniform:
case EvqBuffer:
{
const char* feature = "location qualifier on uniform or buffer";
if (symbol.getAsVariable() == 0)
error(loc, "can only be used on variable declaration", feature, "");
break;
}
default:
break;
}
int repeated = intermediate.addUsedLocation(qualifier, type);
if (repeated >= 0)
error(loc, "repeated use of location", "location", "%d", repeated);
}
if (qualifier.hasBinding()) {
// Binding checking, from the spec:
//
// "If the binding point for any uniform or shader storage block instance is less than zero, or greater than or
// equal to the implementation-dependent maximum number of uniform buffer bindings, a compile-time
// error will occur. When the binding identifier is used with a uniform or shader storage block instanced as
// an array of size N, all elements of the array from binding through binding + N <20> 1 must be within this
// range."
//
if (type.getBasicType() != EbtSampler && type.getBasicType() != EbtBlock)
error(loc, "requires block, or sampler/image, or atomic-counter type", "binding", "");
// TODO: 4.2 functionality: atomic counter: include in test above
if (type.getBasicType() == EbtSampler) {
int lastBinding = qualifier.layoutBinding;
if (type.isArray())
lastBinding += type.getArraySize();
if (lastBinding >= resources.maxCombinedTextureImageUnits)
error(loc, "sampler binding not less than gl_MaxCombinedTextureImageUnits", "binding", type.isArray() ? "(using array)" : "");
}
}
}
// Do layout error checking that can be done within a qualifier proper, not needing to know
// if there are blocks, atomic counters, variables, etc.
void TParseContext::layoutQualifierCheck(TSourceLoc loc, const TQualifier& qualifier)
{
if (qualifier.hasLocation()) {
switch (qualifier.storage) {
case EvqVaryingIn:
{
const char* feature = "location qualifier on input";
if (profile == EEsProfile)
requireStage(loc, EShLangVertex, feature);
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, feature);
if (language == EShLangVertex)
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 330, 0, feature);
else
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 410, GL_ARB_separate_shader_objects, feature);
break;
}
case EvqVaryingOut:
{
const char* feature = "location qualifier on output";
if (profile == EEsProfile)
requireStage(loc, EShLangFragment, feature);
requireStage(loc, (EShLanguageMask)~EShLangComputeMask, feature);
if (language == EShLangFragment)
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 330, 0, feature);
else
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 410, GL_ARB_separate_shader_objects, feature);
break;
}
case EvqUniform:
case EvqBuffer:
{
const char* feature = "location qualifier on uniform or buffer";
requireProfile(loc, ECoreProfile | ECompatibilityProfile, feature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, 0, feature);
break;
}
default:
break;
}
}
if (qualifier.hasBinding()) {
if (qualifier.storage != EvqUniform && qualifier.storage != EvqBuffer)
error(loc, "requires uniform or buffer storage qualifier", "binding", "");
}
if (qualifier.hasStream()) {
if (qualifier.storage != EvqVaryingOut)
error(loc, "can only be used on an output", "stream", "");
}
}
// For places that can't have shader-level layout qualifiers
void TParseContext::checkNoShaderLayouts(TSourceLoc loc, const TShaderQualifiers& shaderQualifiers)
{
const char* message = "can only apply to a standalone qualifier";
if (shaderQualifiers.geometry != ElgNone)
error(loc, message, TQualifier::getGeometryString(shaderQualifiers.geometry), "");
if (shaderQualifiers.invocations > 0)
error(loc, message, "invocations", "");
if (shaderQualifiers.vertices > 0)
error(loc, message, "max_vertices", "");
}
//
// 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(TSourceLoc loc, const TFunction& call, bool& builtIn)
{
const TFunction* function = 0;
if (symbolTable.isFunctionNameVariable(call.getName())) {
error(loc, "can't use function syntax on variable", call.getName().c_str(), "");
return 0;
}
if (profile == EEsProfile || version < 120)
function = findFunctionExact(loc, call, builtIn);
else if (version < 400)
function = findFunction120(loc, call, builtIn);
else
function = findFunction400(loc, call, builtIn);
return function;
}
// Function finding algorithm for ES and desktop 110.
const TFunction* TParseContext::findFunctionExact(TSourceLoc loc, const TFunction& call, bool& builtIn)
{
const TFunction* function = 0;
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol == 0) {
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
return 0;
}
return symbol->getAsFunction();
}
// Function finding algorithm for desktop versions 120 through 330.
const TFunction* TParseContext::findFunction120(TSourceLoc loc, const TFunction& call, bool& builtIn)
{
// first, look for an exact match
TSymbol* symbol = symbolTable.find(call.getMangledName(), &builtIn);
if (symbol)
return symbol->getAsFunction();
// exact match not found, look through a list of overloaded functions of the same name
// "If no exact match is found, then [implicit conversions] will be applied to find a match. Mismatched types
// on input parameters (in or inout or default) must have a conversion from the calling argument type to the
// formal parameter type. Mismatched types on output parameters (out or inout) must have a conversion
// from the formal parameter type to the calling argument type. When argument conversions are used to find
// a match, it is a semantic error if there are multiple ways to apply these conversions to make the call match
// more than one function."
const TFunction* candidate = 0;
TVector<TFunction*> candidateList;
symbolTable.findFunctionNameList(call.getMangledName(), candidateList, builtIn);
int numPossibleMatches = 0;
for (TVector<TFunction*>::const_iterator it = candidateList.begin(); it != candidateList.end(); ++it) {
const TFunction& function = *(*it);
// to even be a potential match, number of arguments has to match
if (call.getParamCount() != function.getParamCount())
continue;
bool possibleMatch = true;
for (int i = 0; i < function.getParamCount(); ++i) {
// same types is easy
if (*function[i].type == *call[i].type)
continue;
// We have a mismatch in type, see if it is implicitly convertible
if (function[i].type->isArray() || call[i].type->isArray() ||
! function[i].type->sameElementShape(*call[i].type))
possibleMatch = false;
else {
// do direction-specific checks for conversion of basic type
TStorageQualifier qualifier = function[i].type->getQualifier().storage;
if (qualifier == EvqIn || qualifier == EvqInOut) {
if (! intermediate.canImplicitlyPromote(call[i].type->getBasicType(), function[i].type->getBasicType()))
possibleMatch = false;
}
if (qualifier == EvqOut || qualifier == EvqInOut) {
if (! intermediate.canImplicitlyPromote(function[i].type->getBasicType(), call[i].type->getBasicType()))
possibleMatch = false;
}
}
if (! possibleMatch)
break;
}
if (possibleMatch) {
if (candidate) {
// our second match, meaning ambiguity
error(loc, "ambiguous function signature match: multiple signatures match under implicit type conversion", call.getName().c_str(), "");
} else
candidate = &function;
}
}
if (candidate == 0)
error(loc, "no matching overloaded function found", call.getName().c_str(), "");
return candidate;
}
// Function finding algorithm for desktop version 400 and above.
const TFunction* TParseContext::findFunction400(TSourceLoc loc, const TFunction& call, bool& builtIn)
{
// TODO: 4.00 functionality: findFunction400()
return findFunction120(loc, call, builtIn);
}
//
// Do everything necessary to handle a variable (non-block) declaration.
// Either redeclaring a variable, or making a new one, updating the symbol
// table, and all error checking.
//
// Returns a subtree node that computes an initializer, if needed.
// Returns 0 if there is no code to execute for initialization.
//
TIntermNode* TParseContext::declareVariable(TSourceLoc loc, TString& identifier, const TPublicType& publicType, TArraySizes* arraySizes, TIntermTyped* initializer)
{
TType type(publicType);
if (voidErrorCheck(loc, identifier, type.getBasicType()))
return 0;
if (! initializer)
nonInitConstCheck(loc, identifier, type);
invariantCheck(loc, type, identifier);
samplerCheck(loc, type, identifier);
// Pick up defaults
if (! type.getQualifier().hasStream() && language == EShLangGeometry && type.getQualifier().storage == EvqVaryingOut)
type.getQualifier().layoutStream = globalOutputDefaults.layoutStream;
if (identifier != "gl_FragCoord" && (publicType.shaderQualifiers.originUpperLeft || publicType.shaderQualifiers.pixelCenterInteger))
error(loc, "can only apply origin_upper_left and pixel_center_origin to gl_FragCoord", "layout qualifier", "");
// Check for redeclaration of built-ins and/or attempting to declare a reserved name
bool newDeclaration = false; // true if a new entry gets added to the symbol table
TSymbol* symbol = redeclareBuiltinVariable(loc, identifier, type.getQualifier(), publicType.shaderQualifiers, newDeclaration);
if (! symbol)
reservedErrorCheck(loc, identifier);
// Declare the variable
if (arraySizes || type.isArray()) {
// Arrayness is potentially coming both from the type and from the
// variable: "int[] a[];" or just one or the other.
// For now, arrays of arrays aren't supported, so it's just one or the
// other. Move it to the type, so all arrayness is part of the type.
arrayDimCheck(loc, &type, arraySizes);
if (arraySizes)
type.setArraySizes(arraySizes);
// for ES, if size isn't coming from an initializer, it has to be explicitly declared now
if (profile == EEsProfile && ! initializer)
arraySizeRequiredCheck(loc, type.getArraySize());
if (! arrayQualifierError(loc, type.getQualifier()))
declareArray(loc, identifier, type, symbol, newDeclaration);
if (initializer) {
profileRequires(loc, ENoProfile, 120, GL_3DL_array_objects, "initializer");
profileRequires(loc, EEsProfile, 300, 0, "initializer");
}
} else {
// non-array case
if (! symbol)
symbol = declareNonArray(loc, identifier, type, newDeclaration);
else if (type != symbol->getType())
error(loc, "cannot change the type of", "redeclaration", symbol->getName().c_str());
}
if (! symbol)
return 0;
// Deal with initializer
TIntermNode* initNode = 0;
if (symbol && initializer) {
TVariable* variable = symbol->getAsVariable();
if (! variable) {
error(loc, "initializer requires a variable, not a member", identifier.c_str(), "");
return 0;
}
initNode = executeInitializer(loc, identifier, initializer, variable);
}
// look for errors/adjustments in layout qualifier use
layoutTypeCheck(loc, *symbol);
// see if it's a linker-level object to track
if (newDeclaration && symbolTable.atGlobalLevel())
intermediate.addSymbolLinkageNode(linkage, *symbol);
return initNode;
}
//
// Declare a non-array variable, the main point being there is no redeclaration
// for resizing allowed.
//
// Return the successfully declared variable.
//
TVariable* TParseContext::declareNonArray(TSourceLoc loc, TString& identifier, TType& type, bool& newDeclaration)
{
// make a new variable
TVariable* variable = new TVariable(&identifier, type);
// add variable to symbol table
if (! symbolTable.insert(*variable)) {
error(loc, "redefinition", variable->getName().c_str(), "");
return 0;
} else {
newDeclaration = true;
return variable;
}
}
//
// Handle all types of initializers from the grammar.
//
// Returning 0 just means there is no code to execute to handle the
// initializer, which will, for example, be the case for constant initalizers.
//
TIntermNode* TParseContext::executeInitializer(TSourceLoc loc, TString& identifier,
TIntermTyped* initializer, TVariable* variable)
{
//
// Identifier must be of type constant, a global, or a temporary, and
// starting at version 120, desktop allows uniforms to have initializers.
//
TStorageQualifier qualifier = variable->getType().getQualifier().storage;
if (! (qualifier == EvqTemporary || qualifier == EvqGlobal || qualifier == EvqConst ||
(qualifier == EvqUniform && profile != EEsProfile && version >= 120))) {
error(loc, " cannot initialize this type of qualifier ", variable->getType().getStorageQualifierString(), "");
return 0;
}
arrayObjectCheck(loc, variable->getType(), "array initializer");
//
// If the initializer was from braces { ... }, we convert the whole subtree to a
// constructor-style subtree, allowing the rest of the code to operate
// identically for both kinds of initializers.
//
initializer = convertInitializerList(loc, variable->getType(), initializer);
if (! initializer) {
// error recovery; don't leave const without constant values
if (qualifier == EvqConst)
variable->getWritableType().getQualifier().storage = EvqTemporary;
return 0;
}
// Fix arrayness if variable is unsized, getting size from the initializer
if (initializer->getType().isArray() && initializer->getType().getArraySize() > 0 &&
variable->getType().isArray() && variable->getType().getArraySize() == 0)
variable->getWritableType().changeArraySize(initializer->getType().getArraySize());
// Uniform and global consts require a constant initializer
if (qualifier == EvqUniform && initializer->getType().getQualifier().storage != EvqConst) {
error(loc, "uniform initializers must be constant", "=", "'%s'", variable->getType().getCompleteString().c_str());
variable->getWritableType().getQualifier().storage = EvqTemporary;
return 0;
}
if (qualifier == EvqConst && symbolTable.atGlobalLevel() && initializer->getType().getQualifier().storage != EvqConst) {
error(loc, "global const initializers must be constant", "=", "'%s'", variable->getType().getCompleteString().c_str());
variable->getWritableType().getQualifier().storage = EvqTemporary;
return 0;
}
// Const variables require a constant initializer, depending on version
if (qualifier == EvqConst) {
if (initializer->getType().getQualifier().storage != EvqConst) {
const char* initFeature = "non-constant initializer";
requireProfile(loc, ECoreProfile | ECompatibilityProfile, initFeature);
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 420, GL_ARB_shading_language_420pack, initFeature);
variable->getWritableType().getQualifier().storage = EvqConstReadOnly;
qualifier = EvqConstReadOnly;
}
}
if (qualifier == EvqConst || qualifier == EvqUniform) {
// Compile-time tagging of the variable with it's constant value...
initializer = intermediate.addConversion(EOpAssign, variable->getType(), initializer);
if (! initializer || ! initializer->getAsConstantUnion() || variable->getType() != initializer->getType()) {
error(loc, "non-matching or non-convertible constant type for const initializer",
variable->getType().getStorageQualifierString(), "");
variable->getWritableType().getQualifier().storage = EvqTemporary;
return 0;
}
variable->setConstArray(initializer->getAsConstantUnion()->getConstArray());
} else {
// normal assigning of a value to a variable...
TIntermSymbol* intermSymbol = intermediate.addSymbol(variable->getUniqueId(), variable->getName(), variable->getType(), loc);
TIntermNode* initNode = intermediate.addAssign(EOpAssign, intermSymbol, initializer, loc);
if (! initNode)
assignError(loc, "=", intermSymbol->getCompleteString(), initializer->getCompleteString());
return initNode;
}
return 0;
}
//
// Reprocess any initalizer-list { ... } parts of the initializer.
// Need to heirarchically assign correct types and implicit
// conversions. Will do this mimicking the same process used for
// creating a constructor-style initializer, ensuring we get the
// same form.
//
TIntermTyped* TParseContext::convertInitializerList(TSourceLoc loc, const TType& type, TIntermTyped* initializer)
{
// Will operate recursively. Once a subtree is found that is constructor style,
// everything below it is already good: Only the "top part" of the initializer
// can be an initializer list, where "top part" can extend for several (or all) levels.
// see if we have bottomed out in the tree within the initializer-list part
TIntermAggregate* initList = initializer->getAsAggregate();
if (! initList || initList->getOp() != EOpNull)
return initializer;
// Of the initializer-list set of nodes, need to process bottom up,
// so recurse deep, then process on the way up.
// Go down the tree here...
if (type.isArray()) {
// The type's array might be unsized, which could be okay, so base sizes on the size of the aggregate.
// Later on, initializer execution code will deal with array size logic.
TType arrayType;
arrayType.shallowCopy(type);
arrayType.setArraySizes(type);
arrayType.changeArraySize(initList->getSequence().size());
TType elementType(arrayType, 0); // dereferenced type
for (size_t i = 0; i < initList->getSequence().size(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, elementType, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == 0)
return 0;
}
return addConstructor(loc, initList, arrayType, mapTypeToConstructorOp(arrayType));
} else if (type.isStruct()) {
if (type.getStruct()->size() != initList->getSequence().size()) {
error(loc, "wrong number of structure members", "initializer list", "");
return 0;
}
for (size_t i = 0; i < type.getStruct()->size(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, *(*type.getStruct())[i].type, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == 0)
return 0;
}
} else if (type.isMatrix()) {
if (type.getMatrixCols() != initList->getSequence().size()) {
error(loc, "wrong number of matrix columns:", "initializer list", type.getCompleteString().c_str());
return 0;
}
TType vectorType(type, 0); // dereferenced type
for (int i = 0; i < type.getMatrixCols(); ++i) {
initList->getSequence()[i] = convertInitializerList(loc, vectorType, initList->getSequence()[i]->getAsTyped());
if (initList->getSequence()[i] == 0)
return 0;
}
} else if (type.isVector()) {
if (type.getVectorSize() != initList->getSequence().size()) {
error(loc, "wrong vector size (or rows in a matrix column):", "initializer list", type.getCompleteString().c_str());
return 0;
}
} else {
error(loc, "unexpected initializer-list type:", "initializer list", type.getCompleteString().c_str());
return 0;
}
// now that the subtree is processed, process this node
return addConstructor(loc, initList, type, mapTypeToConstructorOp(type));
}
//
// Test for the correctness of the parameters passed to various constructor functions
// and also convert them to the right data type, if allowed and required.
//
// Returns 0 for an error or the constructed node (aggregate or typed) for no error.
//
TIntermTyped* TParseContext::addConstructor(TSourceLoc loc, TIntermNode* node, const TType& type, TOperator op)
{
if (node == 0)
return 0;
TIntermAggregate* aggrNode = node->getAsAggregate();
TTypeList::iterator memberTypes;
if (op == EOpConstructStruct)
memberTypes = type.getStruct()->begin();
TType elementType;
elementType.shallowCopy(type);
if (type.isArray())
elementType.dereference();
bool singleArg;
if (aggrNode) {
if (aggrNode->getOp() != EOpNull || aggrNode->getSequence().size() == 1)
singleArg = true;
else
singleArg = false;
} else
singleArg = true;
TIntermTyped *newNode;
if (singleArg) {
// If structure constructor or array constructor is being called
// for only one parameter inside the structure, we need to call constructStruct function once.
if (type.isArray())
newNode = constructStruct(node, elementType, 1, node->getLoc());
else if (op == EOpConstructStruct)
newNode = constructStruct(node, *(*memberTypes).type, 1, node->getLoc());
else
newNode = constructBuiltIn(type, op, node, node->getLoc(), false);
if (newNode && (type.isArray() || op == EOpConstructStruct))
newNode = intermediate.setAggregateOperator(newNode, EOpConstructStruct, type, loc);
return newNode;
}
//
// Handle list of arguments.
//
TIntermSequence &sequenceVector = aggrNode->getSequence(); // Stores the information about the parameter to the constructor
// if the structure constructor contains more than one parameter, then construct
// each parameter
int paramCount = 0; // keeps a track of the constructor parameter number being checked
// for each parameter to the constructor call, check to see if the right type is passed or convert them
// to the right type if possible (and allowed).
// for structure constructors, just check if the right type is passed, no conversion is allowed.
for (TIntermSequence::iterator p = sequenceVector.begin();
p != sequenceVector.end(); p++, paramCount++) {
if (type.isArray())
newNode = constructStruct(*p, elementType, paramCount+1, node->getLoc());
else if (op == EOpConstructStruct)
newNode = constructStruct(*p, *(memberTypes[paramCount]).type, paramCount+1, node->getLoc());
else
newNode = constructBuiltIn(type, op, *p, node->getLoc(), true);
if (newNode)
*p = newNode;
else
return 0;
}
TIntermTyped* constructor = intermediate.setAggregateOperator(aggrNode, op, type, loc);
return constructor;
}
// Function for constructor implementation. Calls addUnaryMath with appropriate EOp value
// for the parameter to the constructor (passed to this function). Essentially, it converts
// the parameter types correctly. If a constructor expects an int (like ivec2) and is passed a
// float, then float is converted to int.
//
// Returns 0 for an error or the constructed node.
//
TIntermTyped* TParseContext::constructBuiltIn(const TType& type, TOperator op, TIntermNode* node, TSourceLoc loc, bool subset)
{
TIntermTyped* newNode;
TOperator basicOp;
//
// First, convert types as needed.
//
switch (op) {
case EOpConstructVec2:
case EOpConstructVec3:
case EOpConstructVec4:
case EOpConstructMat2x2:
case EOpConstructMat2x3:
case EOpConstructMat2x4:
case EOpConstructMat3x2:
case EOpConstructMat3x3:
case EOpConstructMat3x4:
case EOpConstructMat4x2:
case EOpConstructMat4x3:
case EOpConstructMat4x4:
case EOpConstructFloat:
basicOp = EOpConstructFloat;
break;
case EOpConstructDVec2:
case EOpConstructDVec3:
case EOpConstructDVec4:
case EOpConstructDMat2x2:
case EOpConstructDMat2x3:
case EOpConstructDMat2x4:
case EOpConstructDMat3x2:
case EOpConstructDMat3x3:
case EOpConstructDMat3x4:
case EOpConstructDMat4x2:
case EOpConstructDMat4x3:
case EOpConstructDMat4x4:
case EOpConstructDouble:
basicOp = EOpConstructDouble;
break;
case EOpConstructIVec2:
case EOpConstructIVec3:
case EOpConstructIVec4:
case EOpConstructInt:
basicOp = EOpConstructInt;
break;
case EOpConstructUVec2:
case EOpConstructUVec3:
case EOpConstructUVec4:
case EOpConstructUint:
basicOp = EOpConstructUint;
break;
case EOpConstructBVec2:
case EOpConstructBVec3:
case EOpConstructBVec4:
case EOpConstructBool:
basicOp = EOpConstructBool;
break;
default:
error(loc, "unsupported construction", "", "");
return 0;
}
newNode = intermediate.addUnaryMath(basicOp, node, node->getLoc());
if (newNode == 0) {
error(loc, "can't convert", "constructor", "");
return 0;
}
//
// Now, if there still isn't an operation to do the construction, and we need one, add one.
//
// Otherwise, skip out early.
if (subset || (newNode != node && newNode->getType() == type))
return newNode;
// setAggregateOperator will insert a new node for the constructor, as needed.
return intermediate.setAggregateOperator(newNode, op, type, loc);
}
// This function tests for the type of the parameters to the structures constructors. Raises
// an error message if the expected type does not match the parameter passed to the constructor.
//
// Returns 0 for an error or the input node itself if the expected and the given parameter types match.
//
TIntermTyped* TParseContext::constructStruct(TIntermNode* node, const TType& type, int paramCount, TSourceLoc loc)
{
TIntermTyped* converted = intermediate.addConversion(EOpConstructStruct, type, node->getAsTyped());
if (! converted || converted->getType() != type) {
error(loc, "", "constructor", "cannot convert parameter %d from '%s' to '%s'", paramCount,
node->getAsTyped()->getType().getCompleteString().c_str(), type.getCompleteString().c_str());
return 0;
}
return converted;
}
//
// Do everything needed to add an interface block.
//
void TParseContext::declareBlock(TSourceLoc loc, TTypeList& typeList, const TString* instanceName, TArraySizes* arraySizes)
{
if (profile == EEsProfile && arraySizes)
arraySizeRequiredCheck(loc, arraySizes->getSize());
switch (currentBlockQualifier.storage) {
case EvqBuffer:
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "buffer block");
profileRequires(loc, ECoreProfile | ECompatibilityProfile, 430, 0, "buffer block");
break;
case EvqUniform:
profileRequires(loc, EEsProfile, 300, 0, "uniform block");
profileRequires(loc, ENoProfile, 140, 0, "uniform block");
break;
case EvqVaryingIn:
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "input block");
break;
case EvqVaryingOut:
requireProfile(loc, ECoreProfile | ECompatibilityProfile, "output block");
break;
default:
error(loc, "only uniform, buffer, in, or out blocks are supported", blockName->c_str(), "");
return;
}
arrayDimCheck(loc, arraySizes, 0);
// fix and check for member storage qualifiers and types that don't belong within a block
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
TSourceLoc memberLoc = typeList[member].loc;
pipeInOutFix(memberLoc, memberQualifier);
if (memberQualifier.storage != EvqTemporary && memberQualifier.storage != EvqGlobal && memberQualifier.storage != currentBlockQualifier.storage)
error(memberLoc, "member storage qualifier cannot contradict block storage qualifier", typeList[member].type->getFieldName().c_str(), "");
if (currentBlockQualifier.storage == EvqUniform && (memberQualifier.isInterpolation() || memberQualifier.isAuxiliary()))
error(memberLoc, "member of uniform block cannot have an auxiliary or interpolation qualifier", typeList[member].type->getFieldName().c_str(), "");
TBasicType basicType = typeList[member].type->getBasicType();
if (basicType == EbtSampler)
error(memberLoc, "member of block cannot be a sampler type", typeList[member].type->getFieldName().c_str(), "");
}
// This might be a redeclaration of a built-in block. If so, redeclareBuiltinBlock() will
// do all the rest.
if (! symbolTable.atBuiltInLevel() && builtInName(*blockName)) {
redeclareBuiltinBlock(loc, typeList, *blockName, instanceName, arraySizes);
return;
}
// Not a redeclaration of a built-in; check that all names are user names.
reservedErrorCheck(loc, *blockName);
if (instanceName)
reservedErrorCheck(loc, *instanceName);
for (unsigned int member = 0; member < typeList.size(); ++member)
reservedErrorCheck(typeList[member].loc, typeList[member].type->getFieldName());
// Make default block qualification, and adjust the member qualifications
TQualifier defaultQualification;
switch (currentBlockQualifier.storage) {
case EvqBuffer: defaultQualification = globalBufferDefaults; break;
case EvqUniform: defaultQualification = globalUniformDefaults; break;
case EvqVaryingIn: defaultQualification = globalInputDefaults; break;
case EvqVaryingOut: defaultQualification = globalOutputDefaults; break;
default: defaultQualification.clear(); break;
}
// fix and check for member layout qualifiers
mergeObjectLayoutQualifiers(loc, defaultQualification, currentBlockQualifier);
for (unsigned int member = 0; member < typeList.size(); ++member) {
TQualifier& memberQualifier = typeList[member].type->getQualifier();
TSourceLoc memberLoc = typeList[member].loc;
if (memberQualifier.hasStream()) {
if (defaultQualification.layoutStream != memberQualifier.layoutStream)
error(memberLoc, "member cannot contradict block", "stream", "");
}
if (memberQualifier.layoutPacking != ElpNone)
error(memberLoc, "member of block cannot have a packing layout qualifier", typeList[member].type->getFieldName().c_str(), "");
TQualifier newMemberQualification = defaultQualification;
mergeQualifiers(memberLoc, newMemberQualification, memberQualifier, false);
memberQualifier = newMemberQualification;
}
// reverse merge, so that currentBlockQualifier now has all layout information
// (can't use defaultQualification directly, it's missing other non-layout-default-class qualifiers)
mergeObjectLayoutQualifiers(loc, currentBlockQualifier, defaultQualification);
//
// Build and add the interface block as a new type named 'blockName'
//
TType blockType(&typeList, *blockName, currentBlockQualifier);
if (arraySizes)
blockType.setArraySizes(arraySizes);
//
// Don't make a user-defined type out of block name; that will cause an error
// if the same block name gets reused in a different interface.
//
// "Block names have no other use within a shader
// beyond interface matching; it is a compile-time error to use a block name at global scope for anything
// other than as a block name (e.g., use of a block name for a global variable name or function name is
// currently reserved)."
//
// Use the symbol table to prevent normal reuse of the block's name, as a variable entry,
// whose type is EbtBlock, but without all the structure; that will come from the type
// the instances point to.
//
TType blockNameType(EbtBlock, blockType.getQualifier().storage);
TVariable* blockNameVar = new TVariable(blockName, blockNameType);
if (! symbolTable.insert(*blockNameVar)) {
TSymbol* existingName = symbolTable.find(*blockName);
if (existingName->getType().getBasicType() == EbtBlock) {
if (existingName->getType().getQualifier().storage == blockType.getQualifier().storage) {
error(loc, "Cannot reuse block name within the same interface:", blockName->c_str(), blockType.getStorageQualifierString());
return;
}
} else {
error(loc, "block name cannot redefine a non-block name", blockName->c_str(), "");
return;
}
}
// Add the variable, as anonymous or named instanceName.
// Make an anonymous variable if no name was provided.
if (! instanceName)
instanceName = NewPoolTString("");
TVariable& variable = *new TVariable(instanceName, blockType);
if (! symbolTable.insert(variable)) {
if (*instanceName == "")
error(loc, "nameless block contains a member that already has a name at global scope", blockName->c_str(), "");
else
error(loc, "block instance name redefinition", variable.getName().c_str(), "");
return;
}
// Check for general layout qualifier errors
layoutTypeCheck(loc, variable);
// Save it in the AST for linker use.
intermediate.addSymbolLinkageNode(linkage, variable);
}
// For an identifier that is already declared, add more qualification to it.
void TParseContext::addQualifierToExisting(TSourceLoc loc, TQualifier qualifier, const TString& identifier)
{
TSymbol* symbol = symbolTable.find(identifier);
if (! symbol) {
error(loc, "identifier not previously declared", identifier.c_str(), "");
return;
}
if (symbol->getAsFunction()) {
error(loc, "cannot re-qualify a function name", identifier.c_str(), "");
return;
}
if (qualifier.isAuxiliary() ||
qualifier.isMemory() ||
qualifier.isInterpolation() ||
qualifier.storage != EvqTemporary ||
qualifier.precision != EpqNone) {
error(loc, "cannot add storage, auxiliary, memory, interpolation, or precision qualifier to an existing variable", identifier.c_str(), "");
return;
}
// For read-only built-ins, add a new symbol for holding the modified qualifier.
// This will bring up an entire block, if a block type has to be modified (e.g., gl_Position inside a block)
if (symbol->isReadOnly())
symbol = symbolTable.copyUp(symbol);
if (qualifier.invariant) {
if (intermediate.inIoAccessed(identifier))
error(loc, "cannot change qualification after use", "invariant", "");
symbol->getWritableType().getQualifier().invariant = true;
invariantCheck(loc, symbol->getType(), identifier);
}
}
void TParseContext::addQualifierToExisting(TSourceLoc loc, TQualifier qualifier, TIdentifierList& identifiers)
{
for (unsigned int i = 0; i < identifiers.size(); ++i)
addQualifierToExisting(loc, qualifier, *identifiers[i]);
}
void TParseContext::invariantCheck(TSourceLoc loc, const TType& type, const TString& identifier)
{
if (! type.getQualifier().invariant)
return;
bool pipeOut = type.getQualifier().isPipeOutput();
bool pipeIn = type.getQualifier().isPipeInput();
if (version >= 300 || profile != EEsProfile && version >= 420) {
if (! pipeOut)
error(loc, "can only apply to an output:", "invariant", identifier.c_str());
} else {
if ((language == EShLangVertex && pipeIn) || (! pipeOut && ! pipeIn))
error(loc, "can only apply to an output or an input in a non-vertex stage\n", "invariant", "");
}
}
//
// Updating default qualifier for the case of a declaration with just a qualifier,
// no type, block, or identifier.
//
void TParseContext::updateStandaloneQualifierDefaults(TSourceLoc loc, const TPublicType& publicType)
{
if (publicType.shaderQualifiers.vertices) {
if (! intermediate.setVertices(publicType.shaderQualifiers.vertices)) {
if (language == EShLangGeometry)
error(loc, "cannot change previously set layout value", "max_vertices", "");
else
error(loc, "cannot change previously set layout value", "vertices", "");
}
}
if (publicType.shaderQualifiers.invocations) {
if (! intermediate.setInvocations(publicType.shaderQualifiers.invocations))
error(loc, "cannot change previously set layout value", "invocations", "");
}
if (publicType.shaderQualifiers.geometry != ElgNone) {
if (publicType.qualifier.storage == EvqVaryingIn) {
switch (publicType.shaderQualifiers.geometry) {
case ElgPoints:
case ElgLines:
case ElgLinesAdjacency:
case ElgTriangles:
case ElgTrianglesAdjacency:
case ElgQuads:
case ElgIsolines:
if (intermediate.setInputPrimitive(publicType.shaderQualifiers.geometry))
checkInputArrayConsistency(loc);
else
error(loc, "cannot change previously set input primitive", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
default:
error(loc, "does not apply to input", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
}
} else if (publicType.qualifier.storage == EvqVaryingOut) {
switch (publicType.shaderQualifiers.geometry) {
case ElgPoints:
case ElgLineStrip:
case ElgTriangleStrip:
if (! intermediate.setOutputPrimitive(publicType.shaderQualifiers.geometry))
error(loc, "cannot change previously set output primitive", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
break;
default:
error(loc, "does not only apply to output", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
}
} else
error(loc, "cannot be used here", TQualifier::getGeometryString(publicType.shaderQualifiers.geometry), "");
}
if (publicType.shaderQualifiers.spacing != EvsNone) {
if (! intermediate.setVertexSpacing(publicType.shaderQualifiers.spacing))
error(loc, "cannot change previously set vertex spacing", TQualifier::getVertexSpacingString(publicType.shaderQualifiers.spacing), "");
}
if (publicType.shaderQualifiers.order != EvoNone) {
if (! intermediate.setVertexOrder(publicType.shaderQualifiers.order))
error(loc, "cannot change previously set vertex order", TQualifier::getVertexOrderString(publicType.shaderQualifiers.order), "");
}
if (publicType.shaderQualifiers.pointMode)
intermediate.setPointMode();
const TQualifier& qualifier = publicType.qualifier;
if (qualifier.isAuxiliary() ||
qualifier.isMemory() ||
qualifier.isInterpolation() ||
qualifier.precision != EpqNone)
error(loc, "cannot use auxiliary, memory, interpolation, or precision qualifier in a default qualifier declaration (declaration with no type)", "", "");
layoutQualifierCheck(loc, qualifier);
switch (qualifier.storage) {
case EvqUniform:
if (qualifier.layoutMatrix != ElmNone)
globalUniformDefaults.layoutMatrix = qualifier.layoutMatrix;
if (qualifier.layoutPacking != ElpNone)
globalUniformDefaults.layoutPacking = qualifier.layoutPacking;
break;
case EvqBuffer:
if (qualifier.layoutMatrix != ElmNone)
globalBufferDefaults.layoutMatrix = qualifier.layoutMatrix;
if (qualifier.layoutPacking != ElpNone)
globalBufferDefaults.layoutPacking = qualifier.layoutPacking;
break;
case EvqVaryingIn:
break;
case EvqVaryingOut:
if (qualifier.hasStream())
globalOutputDefaults.layoutStream = qualifier.layoutStream;
break;
default:
error(loc, "default qualifier requires 'uniform', 'buffer', 'in', or 'out' storage qualification", "", "");
return;
}
if (qualifier.hasBinding())
error(loc, "cannot declare a default, include a type or full declaration", "binding", "");
if (qualifier.hasLocation())
error(loc, "cannot declare a default, use a full declaration", "location", "");
}
//
// Update defaults for qualifiers when declared with a type, and optionally an identifier.
// (But, not the case of just a qualifier.)
//
void TParseContext::updateTypedDefaults(TSourceLoc loc, const TQualifier& qualifier, const TString* id)
{
if (! id) {
if (qualifier.hasLayout())
warn(loc, "cannot set qualifier defaults when using a type and no identifier", "", "");
return;
}
switch (qualifier.storage) {
case EvqBuffer:
case EvqUniform:
if (qualifier.layoutMatrix != ElmNone)
error(loc, "cannot specify matrix layout on a variable declaration", id->c_str(), "");
if (qualifier.layoutPacking != ElpNone)
error(loc, "cannot specify packing on a variable declaration", id->c_str(), "");
break;
case EvqVaryingIn:
if (qualifier.hasLocation())
globalInputDefaults.layoutSlotLocation = qualifier.layoutSlotLocation;
break;
case EvqVaryingOut:
if (qualifier.hasLocation())
globalOutputDefaults.layoutSlotLocation = qualifier.layoutSlotLocation;
break;
default:
if (qualifier.layoutMatrix != ElmNone ||
qualifier.layoutPacking != ElpNone)
error(loc, "layout qualifiers for matrix layout and packing only apply to uniform or buffer blocks", id->c_str(), "");
else if (qualifier.hasLocation())
error(loc, "location qualifiers only appy to uniform, buffer, in, or out storage qualifiers", id->c_str(), "");
}
}
//
// Take the sequence of statements that has been built up since the last case/default,
// put it on the list of top-level nodes for the current (inner-most) switch statement,
// and follow that by the case/default we are on now. (See switch topology comment on
// TIntermSwitch.)
//
void TParseContext::wrapupSwitchSubsequence(TIntermAggregate* statements, TIntermNode* branchNode)
{
TIntermSequence* switchSequence = switchSequenceStack.back();
if (statements) {
if (switchSequence->size() == 0)
error(statements->getLoc(), "cannot have statements before first case/default label", "switch", "");
statements->setOperator(EOpSequence);
switchSequence->push_back(statements);
}
if (branchNode) {
// check all previous cases for the same label (or both are 'default')
for (unsigned int s = 0; s < switchSequence->size(); ++s) {
TIntermBranch* prevBranch = (*switchSequence)[s]->getAsBranchNode();
if (prevBranch) {
TIntermTyped* prevExpression = prevBranch->getExpression();
TIntermTyped* newExpression = branchNode->getAsBranchNode()->getExpression();
if (prevExpression == 0 && newExpression == 0)
error(branchNode->getLoc(), "duplicate label", "default", "");
else if (prevExpression != 0 &&
newExpression != 0 &&
prevExpression->getAsConstantUnion() &&
newExpression->getAsConstantUnion() &&
prevExpression->getAsConstantUnion()->getConstArray()[0].getIConst() ==
newExpression->getAsConstantUnion()->getConstArray()[0].getIConst())
error(branchNode->getLoc(), "duplicated value", "case", "");
}
}
switchSequence->push_back(branchNode);
}
}
//
// Turn the top-level node sequence built up of wrapupSwitchSubsequence9)
// into a switch node.
//
TIntermNode* TParseContext::addSwitch(TSourceLoc loc, TIntermTyped* expression, TIntermAggregate* lastStatements)
{
profileRequires(loc, EEsProfile, 300, 0, "switch statements");
profileRequires(loc, ENoProfile, 130, 0, "switch statements");
wrapupSwitchSubsequence(lastStatements, 0);
if (expression == 0 ||
(expression->getBasicType() != EbtInt && expression->getBasicType() != EbtUint) ||
expression->getType().isArray() || expression->getType().isMatrix() || expression->getType().isVector())
error(loc, "condition must be a scalar integer expression", "switch", "");
// If there is nothing to do, drop the switch but still execute the expression
TIntermSequence* switchSequence = switchSequenceStack.back();
if (switchSequence->size() == 0)
return expression;
if (lastStatements == 0) {
error(loc, "last case/default label must be followed by statements", "switch", "");
return expression;
}
TIntermAggregate* body = new TIntermAggregate(EOpSequence);
body->getSequence() = *switchSequenceStack.back();
body->setLoc(loc);
TIntermSwitch* switchNode = new TIntermSwitch(expression, body);
switchNode->setLoc(loc);
return switchNode;
}
} // end namespace glslang