PR #577 addresses most but not all of the intrinsic promotion problems. This PR resolves all known cases in the remainder. Interlocked ops need special promotion rules because at the time of function selection, the first argument has not been converted to a buffer object. It's just an int or uint, but you don't want to convert THAT argument, because that implies converting the buffer object itself. Rather, you can convert other arguments, but want to stay in the same "family" of functions. E.g, if the first interlocked arg is a uint, use only the uint family, never the int family, you can convert the other args as you please. This PR allows making such opcode and arg specific choices by passing the op and arg to the convertible lambda. The code in the new test "hlsl.promote.atomic.frag" would not compile without this change, but it must compile. Also, it provides better handling of downconversions (to "worse" types), which are permitted in HLSL. The existing method of selecting upconversions is unchanged, but if that doesn't find any valid ones, then it will allow downconversions. In effect this always uses an upconversion if there is one.
482 lines
16 KiB
C++
482 lines
16 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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
//
|
|
// 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:
|
|
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;
|
|
case EvqBuffer:
|
|
if (node->getQualifier().readonly)
|
|
message = "can't modify a readonly buffer";
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// Type that can't be written to?
|
|
//
|
|
switch (node->getBasicType()) {
|
|
case EbtSampler:
|
|
message = "can't modify a sampler";
|
|
break;
|
|
case EbtAtomicUint:
|
|
message = "can't modify an atomic_uint";
|
|
break;
|
|
case EbtVoid:
|
|
message = "can't modify void";
|
|
break;
|
|
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:
|
|
rValueErrorCheck(loc, op, binaryNode->getLeft());
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
TIntermSymbol* symNode = node->getAsSymbolNode();
|
|
if (symNode && symNode->getQualifier().writeonly)
|
|
error(loc, "can't read from writeonly object: ", op, symNode->getName().c_str());
|
|
}
|
|
|
|
// Add a linkage symbol node for 'symbol', which
|
|
// must have its type fully edited, as this will snapshot the type.
|
|
// It is okay if symbol becomes invalid before finish().
|
|
void TParseContextBase::trackLinkage(TSymbol& symbol)
|
|
{
|
|
if (!parsingBuiltins)
|
|
intermediate.addSymbolLinkageNode(linkage, symbol);
|
|
}
|
|
|
|
// 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.
|
|
void TParseContextBase::trackLinkageDeferred(TSymbol& symbol)
|
|
{
|
|
if (!parsingBuiltins)
|
|
linkageSymbols.push_back(&symbol);
|
|
}
|
|
|
|
// 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)
|
|
trackLinkageDeferred(*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 has to match
|
|
if (call.getParamCount() != candidate.getParamCount())
|
|
continue;
|
|
|
|
// see if arguments are convertible
|
|
bool viable = true;
|
|
for (int param = 0; param < candidate.getParamCount(); ++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...
|
|
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 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);
|
|
if (betterParam(*incumbent, candidate))
|
|
tie = true;
|
|
}
|
|
|
|
return incumbent;
|
|
}
|
|
|
|
//
|
|
// 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(TSourceLoc& loc, TType& memberType, TString& memberName)
|
|
{
|
|
// make the global block, if not yet made
|
|
if (globalUniformBlock == nullptr) {
|
|
TString& blockName = *NewPoolTString(getGlobalUniformBlockName());
|
|
TQualifier blockQualifier;
|
|
blockQualifier.clear();
|
|
blockQualifier.storage = EvqUniform;
|
|
TType blockType(new TTypeList, blockName, blockQualifier);
|
|
TString* instanceName = NewPoolTString("");
|
|
globalUniformBlock = new TVariable(instanceName, blockType, true);
|
|
firstNewMember = 0;
|
|
}
|
|
|
|
// add the requested member as a member to the block
|
|
TType* type = new TType;
|
|
type->shallowCopy(memberType);
|
|
type->setFieldName(memberName);
|
|
TTypeLoc typeLoc = {type, loc};
|
|
globalUniformBlock->getType().getWritableStruct()->push_back(typeLoc);
|
|
}
|
|
|
|
//
|
|
// Insert into the symbol table the global uniform block created in
|
|
// growGlobalUniformBlock(). The variables added as members won't be
|
|
// found unless this is done.
|
|
//
|
|
bool TParseContextBase::insertGlobalUniformBlock()
|
|
{
|
|
if (globalUniformBlock == nullptr)
|
|
return true;
|
|
|
|
int numMembers = (int)globalUniformBlock->getType().getStruct()->size();
|
|
bool inserted = false;
|
|
if (firstNewMember == 0) {
|
|
// This is the first request; we need a normal symbol table insert
|
|
inserted = symbolTable.insert(*globalUniformBlock);
|
|
if (inserted)
|
|
trackLinkageDeferred(*globalUniformBlock);
|
|
} else if (firstNewMember <= numMembers) {
|
|
// This is a follow-on request; we need to amend the first insert
|
|
inserted = symbolTable.amend(*globalUniformBlock, firstNewMember);
|
|
}
|
|
|
|
if (inserted) {
|
|
finalizeGlobalUniformBlockLayout(*globalUniformBlock);
|
|
firstNewMember = numMembers;
|
|
}
|
|
|
|
return inserted;
|
|
}
|
|
|
|
void TParseContextBase::finish()
|
|
{
|
|
if (!parsingBuiltins) {
|
|
// Transfer te linkage symbols to AST nodes
|
|
for (auto i = linkageSymbols.begin(); i != linkageSymbols.end(); ++i)
|
|
intermediate.addSymbolLinkageNode(linkage, **i);
|
|
intermediate.addSymbolLinkageNodes(linkage, getLanguage(), symbolTable);
|
|
}
|
|
}
|
|
|
|
} // end namespace glslang
|