| // |
| // 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 |