glslang/glslang/MachineIndependent/ParseContextBase.cpp
John Kessenich deec1933e9 Web: Turn off includes, independent preprocessing path, fine tune all.
Saved about 21K, size down to 380K of MSVC x86 code.
Fixed one bug that needs to be looked at on the master branch:
The test for needing a Vulkan binding has a bug in it, "!layoutAttachment"
which does not mean "no layoutAttachment", because that is non-zero.
This is why some test and test results changed.
2019-08-20 23:21:56 -06:00

639 lines
22 KiB
C++

//
// Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
// Copyright (C) 2016 Google, 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.
//
// Implement the TParseContextBase class.
#include <cstdarg>
#include "ParseHelper.h"
extern int yyparse(glslang::TParseContext*);
namespace glslang {
//
// Used to output syntax, parsing, and semantic errors.
//
void TParseContextBase::outputMessage(const TSourceLoc& loc, const char* szReason,
const char* szToken,
const char* szExtraInfoFormat,
TPrefixType prefix, va_list args)
{
const int maxSize = MaxTokenLength + 200;
char szExtraInfo[maxSize];
safe_vsprintf(szExtraInfo, maxSize, szExtraInfoFormat, args);
infoSink.info.prefix(prefix);
infoSink.info.location(loc);
infoSink.info << "'" << szToken << "' : " << szReason << " " << szExtraInfo << "\n";
if (prefix == EPrefixError) {
++numErrors;
}
}
#if !defined(GLSLANG_WEB) || defined(GLSLANG_WEB_DEVEL)
void C_DECL TParseContextBase::error(const TSourceLoc& loc, const char* szReason, const char* szToken,
const char* szExtraInfoFormat, ...)
{
if (messages & EShMsgOnlyPreprocessor)
return;
va_list args;
va_start(args, szExtraInfoFormat);
outputMessage(loc, szReason, szToken, szExtraInfoFormat, EPrefixError, args);
va_end(args);
if ((messages & EShMsgCascadingErrors) == 0)
currentScanner->setEndOfInput();
}
void C_DECL TParseContextBase::warn(const TSourceLoc& loc, const char* szReason, const char* szToken,
const char* szExtraInfoFormat, ...)
{
if (suppressWarnings())
return;
va_list args;
va_start(args, szExtraInfoFormat);
outputMessage(loc, szReason, szToken, szExtraInfoFormat, EPrefixWarning, args);
va_end(args);
}
void C_DECL TParseContextBase::ppError(const TSourceLoc& loc, const char* szReason, const char* szToken,
const char* szExtraInfoFormat, ...)
{
va_list args;
va_start(args, szExtraInfoFormat);
outputMessage(loc, szReason, szToken, szExtraInfoFormat, EPrefixError, args);
va_end(args);
if ((messages & EShMsgCascadingErrors) == 0)
currentScanner->setEndOfInput();
}
void C_DECL TParseContextBase::ppWarn(const TSourceLoc& loc, const char* szReason, const char* szToken,
const char* szExtraInfoFormat, ...)
{
va_list args;
va_start(args, szExtraInfoFormat);
outputMessage(loc, szReason, szToken, szExtraInfoFormat, EPrefixWarning, args);
va_end(args);
}
#endif
//
// 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 there was an error.
//
bool TParseContextBase::lValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
TIntermBinary* binaryNode = node->getAsBinaryNode();
if (binaryNode) {
switch(binaryNode->getOp()) {
case EOpIndexDirect:
case EOpIndexIndirect: // fall through
case EOpIndexDirectStruct: // fall through
case EOpVectorSwizzle:
case EOpMatrixSwizzle:
return lValueErrorCheck(loc, op, binaryNode->getLeft());
default:
break;
}
error(loc, " l-value required", op, "", "");
return true;
}
const char* symbol = nullptr;
TIntermSymbol* symNode = node->getAsSymbolNode();
if (symNode != nullptr)
symbol = symNode->getName().c_str();
const char* message = nullptr;
switch (node->getQualifier().storage) {
case EvqConst: message = "can't modify a const"; break;
case EvqConstReadOnly: message = "can't modify a const"; break;
case EvqUniform: message = "can't modify a uniform"; break;
#ifndef GLSLANG_WEB
case EvqBuffer:
if (node->getQualifier().isReadOnly())
message = "can't modify a readonly buffer";
if (node->getQualifier().isShaderRecordNV())
message = "can't modify a shaderrecordnv qualified buffer";
break;
case EvqHitAttrNV:
if (language != EShLangIntersectNV)
message = "cannot modify hitAttributeNV in this stage";
break;
#endif
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;
#ifndef GLSLANG_WEB
case EbtAtomicUint:
message = "can't modify an atomic_uint";
break;
case EbtAccStructNV:
message = "can't modify accelerationStructureNV";
break;
#endif
default:
break;
}
}
if (message == nullptr && binaryNode == nullptr && symNode == nullptr) {
error(loc, " l-value required", op, "", "");
return true;
}
//
// Everything else is okay, no error.
//
if (message == nullptr)
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;
}
// Test for and give an error if the node can't be read from.
void TParseContextBase::rValueErrorCheck(const TSourceLoc& loc, const char* op, TIntermTyped* node)
{
if (! node)
return;
TIntermBinary* binaryNode = node->getAsBinaryNode();
if (binaryNode) {
switch(binaryNode->getOp()) {
case EOpIndexDirect:
case EOpIndexIndirect:
case EOpIndexDirectStruct:
case EOpVectorSwizzle:
case EOpMatrixSwizzle:
rValueErrorCheck(loc, op, binaryNode->getLeft());
default:
break;
}
return;
}
TIntermSymbol* symNode = node->getAsSymbolNode();
if (symNode && symNode->getQualifier().isWriteOnly())
error(loc, "can't read from writeonly object: ", op, symNode->getName().c_str());
}
// Add 'symbol' to the list of deferred linkage symbols, which
// are later processed in finish(), at which point the symbol
// must still be valid.
// It is okay if the symbol's type will be subsequently edited;
// the modifications will be tracked.
// Order is preserved, to avoid creating novel forward references.
void TParseContextBase::trackLinkage(TSymbol& symbol)
{
if (!parsingBuiltins)
linkageSymbols.push_back(&symbol);
}
// Ensure index is in bounds, correct if necessary.
// Give an error if not.
void TParseContextBase::checkIndex(const TSourceLoc& loc, const TType& type, int& index)
{
const auto sizeIsSpecializationExpression = [&type]() {
return type.containsSpecializationSize() &&
type.getArraySizes()->getOuterNode() != nullptr &&
type.getArraySizes()->getOuterNode()->getAsSymbolNode() == nullptr; };
if (index < 0) {
error(loc, "", "[", "index out of range '%d'", index);
index = 0;
} else if (type.isArray()) {
if (type.isSizedArray() && !sizeIsSpecializationExpression() &&
index >= type.getOuterArraySize()) {
error(loc, "", "[", "array index out of range '%d'", index);
index = type.getOuterArraySize() - 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;
}
}
}
// Make a shared symbol have a non-shared version that can be edited by the current
// compile, such that editing its type will not change the shared version and will
// effect all nodes already sharing it (non-shallow type),
// or adopting its full type after being edited (shallow type).
void TParseContextBase::makeEditable(TSymbol*& symbol)
{
// copyUp() does a deep copy of the type.
symbol = symbolTable.copyUp(symbol);
// Save it (deferred, so it can be edited first) in the AST for linker use.
if (symbol)
trackLinkage(*symbol);
}
// Return a writable version of the variable 'name'.
//
// Return nullptr if 'name' is not found. This should mean
// something is seriously wrong (e.g., compiler asking self for
// built-in that doesn't exist).
TVariable* TParseContextBase::getEditableVariable(const char* name)
{
bool builtIn;
TSymbol* symbol = symbolTable.find(name, &builtIn);
assert(symbol != nullptr);
if (symbol == nullptr)
return nullptr;
if (builtIn)
makeEditable(symbol);
return symbol->getAsVariable();
}
// Select the best matching function for 'call' from 'candidateList'.
//
// Assumptions
//
// There is no exact match, so a selection algorithm needs to run. That is, the
// language-specific handler should check for exact match first, to
// decide what to do, before calling this selector.
//
// Input
//
// * list of candidate signatures to select from
// * the call
// * a predicate function convertible(from, to) that says whether or not type
// 'from' can implicitly convert to type 'to' (it includes the case of what
// the calling language would consider a matching type with no conversion
// needed)
// * a predicate function better(from1, from2, to1, to2) that says whether or
// not a conversion from <-> to2 is considered better than a conversion
// from <-> to1 (both in and out directions need testing, as declared by the
// formal parameter)
//
// Output
//
// * best matching candidate (or none, if no viable candidates found)
// * whether there was a tie for the best match (ambiguous overload selection,
// caller's choice for how to report)
//
const TFunction* TParseContextBase::selectFunction(
const TVector<const TFunction*> candidateList,
const TFunction& call,
std::function<bool(const TType& from, const TType& to, TOperator op, int arg)> convertible,
std::function<bool(const TType& from, const TType& to1, const TType& to2)> better,
/* output */ bool& tie)
{
//
// Operation
//
// 1. Prune the input list of candidates down to a list of viable candidates,
// where each viable candidate has
//
// * at least as many parameters as there are calling arguments, with any
// remaining parameters being optional or having default values
// * each parameter is true under convertible(A, B), where A is the calling
// type for in and B is the formal type, and in addition, for out B is the
// calling type and A is the formal type
//
// 2. If there are no viable candidates, return with no match.
//
// 3. If there is only one viable candidate, it is the best match.
//
// 4. If there are multiple viable candidates, select the first viable candidate
// as the incumbent. Compare the incumbent to the next viable candidate, and if
// that candidate is better (bullets below), make it the incumbent. Repeat, with
// a linear walk through the viable candidate list. The final incumbent will be
// returned as the best match. A viable candidate is better than the incumbent if
//
// * it has a function argument with a better(...) conversion than the incumbent,
// for all directions needed by in and out
// * the incumbent has no argument with a better(...) conversion then the
// candidate, for either in or out (as needed)
//
// 5. Check for ambiguity by comparing the best match against all other viable
// candidates. If any other viable candidate has a function argument with a
// better(...) conversion than the best candidate (for either in or out
// directions), return that there was a tie for best.
//
tie = false;
// 1. prune to viable...
TVector<const TFunction*> viableCandidates;
for (auto it = candidateList.begin(); it != candidateList.end(); ++it) {
const TFunction& candidate = *(*it);
// to even be a potential match, number of arguments must be >= the number of
// fixed (non-default) parameters, and <= the total (including parameter with defaults).
if (call.getParamCount() < candidate.getFixedParamCount() ||
call.getParamCount() > candidate.getParamCount())
continue;
// see if arguments are convertible
bool viable = true;
// The call can have fewer parameters than the candidate, if some have defaults.
const int paramCount = std::min(call.getParamCount(), candidate.getParamCount());
for (int param = 0; param < paramCount; ++param) {
if (candidate[param].type->getQualifier().isParamInput()) {
if (! convertible(*call[param].type, *candidate[param].type, candidate.getBuiltInOp(), param)) {
viable = false;
break;
}
}
if (candidate[param].type->getQualifier().isParamOutput()) {
if (! convertible(*candidate[param].type, *call[param].type, candidate.getBuiltInOp(), param)) {
viable = false;
break;
}
}
}
if (viable)
viableCandidates.push_back(&candidate);
}
// 2. none viable...
if (viableCandidates.size() == 0)
return nullptr;
// 3. only one viable...
if (viableCandidates.size() == 1)
return viableCandidates.front();
// 4. find best...
const auto betterParam = [&call, &better](const TFunction& can1, const TFunction& can2) -> bool {
// is call -> can2 better than call -> can1 for any parameter
bool hasBetterParam = false;
for (int param = 0; param < call.getParamCount(); ++param) {
if (better(*call[param].type, *can1[param].type, *can2[param].type)) {
hasBetterParam = true;
break;
}
}
return hasBetterParam;
};
const auto equivalentParams = [&call, &better](const TFunction& can1, const TFunction& can2) -> bool {
// is call -> can2 equivalent to call -> can1 for all the call parameters?
for (int param = 0; param < call.getParamCount(); ++param) {
if (better(*call[param].type, *can1[param].type, *can2[param].type) ||
better(*call[param].type, *can2[param].type, *can1[param].type))
return false;
}
return true;
};
const TFunction* incumbent = viableCandidates.front();
for (auto it = viableCandidates.begin() + 1; it != viableCandidates.end(); ++it) {
const TFunction& candidate = *(*it);
if (betterParam(*incumbent, candidate) && ! betterParam(candidate, *incumbent))
incumbent = &candidate;
}
// 5. ambiguity...
for (auto it = viableCandidates.begin(); it != viableCandidates.end(); ++it) {
if (incumbent == *it)
continue;
const TFunction& candidate = *(*it);
// In the case of default parameters, it may have an identical initial set, which is
// also ambiguous
if (betterParam(*incumbent, candidate) || equivalentParams(*incumbent, candidate))
tie = true;
}
return incumbent;
}
//
// Look at a '.' field selector string and change it into numerical selectors
// for a vector or scalar.
//
// Always return some form of swizzle, so the result is always usable.
//
void TParseContextBase::parseSwizzleSelector(const TSourceLoc& loc, const TString& compString, int vecSize,
TSwizzleSelectors<TVectorSelector>& selector)
{
// Too long?
if (compString.size() > MaxSwizzleSelectors)
error(loc, "vector swizzle too long", compString.c_str(), "");
// Use this to test that all swizzle characters are from the same swizzle-namespace-set
enum {
exyzw,
ergba,
estpq,
} fieldSet[MaxSwizzleSelectors];
// Decode the swizzle string.
int size = std::min(MaxSwizzleSelectors, (int)compString.size());
for (int i = 0; i < size; ++i) {
switch (compString[i]) {
case 'x':
selector.push_back(0);
fieldSet[i] = exyzw;
break;
case 'r':
selector.push_back(0);
fieldSet[i] = ergba;
break;
case 's':
selector.push_back(0);
fieldSet[i] = estpq;
break;
case 'y':
selector.push_back(1);
fieldSet[i] = exyzw;
break;
case 'g':
selector.push_back(1);
fieldSet[i] = ergba;
break;
case 't':
selector.push_back(1);
fieldSet[i] = estpq;
break;
case 'z':
selector.push_back(2);
fieldSet[i] = exyzw;
break;
case 'b':
selector.push_back(2);
fieldSet[i] = ergba;
break;
case 'p':
selector.push_back(2);
fieldSet[i] = estpq;
break;
case 'w':
selector.push_back(3);
fieldSet[i] = exyzw;
break;
case 'a':
selector.push_back(3);
fieldSet[i] = ergba;
break;
case 'q':
selector.push_back(3);
fieldSet[i] = estpq;
break;
default:
error(loc, "unknown swizzle selection", compString.c_str(), "");
break;
}
}
// Additional error checking.
for (int i = 0; i < selector.size(); ++i) {
if (selector[i] >= vecSize) {
error(loc, "vector swizzle selection out of range", compString.c_str(), "");
selector.resize(i);
break;
}
if (i > 0 && fieldSet[i] != fieldSet[i-1]) {
error(loc, "vector swizzle selectors not from the same set", compString.c_str(), "");
selector.resize(i);
break;
}
}
// Ensure it is valid.
if (selector.size() == 0)
selector.push_back(0);
}
#ifdef ENABLE_HLSL
//
// Make the passed-in variable information become a member of the
// global uniform block. If this doesn't exist yet, make it.
//
void TParseContextBase::growGlobalUniformBlock(const TSourceLoc& loc, TType& memberType, const TString& memberName, TTypeList* typeList)
{
// Make the global block, if not yet made.
if (globalUniformBlock == nullptr) {
TQualifier blockQualifier;
blockQualifier.clear();
blockQualifier.storage = EvqUniform;
TType blockType(new TTypeList, *NewPoolTString(getGlobalUniformBlockName()), blockQualifier);
setUniformBlockDefaults(blockType);
globalUniformBlock = new TVariable(NewPoolTString(""), blockType, true);
firstNewMember = 0;
}
// Update with binding and set
globalUniformBlock->getWritableType().getQualifier().layoutBinding = globalUniformBinding;
globalUniformBlock->getWritableType().getQualifier().layoutSet = globalUniformSet;
// Add the requested member as a member to the global block.
TType* type = new TType;
type->shallowCopy(memberType);
type->setFieldName(memberName);
if (typeList)
type->setStruct(typeList);
TTypeLoc typeLoc = {type, loc};
globalUniformBlock->getType().getWritableStruct()->push_back(typeLoc);
// Insert into the symbol table.
if (firstNewMember == 0) {
// This is the first request; we need a normal symbol table insert
if (symbolTable.insert(*globalUniformBlock))
trackLinkage(*globalUniformBlock);
else
error(loc, "failed to insert the global constant buffer", "uniform", "");
} else {
// This is a follow-on request; we need to amend the first insert
symbolTable.amend(*globalUniformBlock, firstNewMember);
}
++firstNewMember;
}
#endif
void TParseContextBase::finish()
{
if (parsingBuiltins)
return;
// Transfer the linkage symbols to AST nodes, preserving order.
TIntermAggregate* linkage = new TIntermAggregate;
for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i)
intermediate.addSymbolLinkageNode(linkage, **i);
intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable);
}
} // end namespace glslang