HLSL: Build IO types bottom up, as parsed, and cache the original (IO).

Previously, this was done recursively, per object, and the nonIO version
was cached. This reverses both those approaches.
This commit is contained in:
John Kessenich
2017-02-03 17:57:55 -07:00
parent 88c4464df5
commit 727b374fd3
7 changed files with 295 additions and 324 deletions

View File

@@ -1708,14 +1708,7 @@ bool HlslGrammar::acceptStruct(TType& type)
new(&type) TType(typeList, structName, postDeclQualifier); // sets EbtBlock
}
// If it was named, which means the type can be reused later, add
// it to the symbol table. (Unless it's a block, in which
// case the name is not a type.)
if (type.getBasicType() != EbtBlock && structName.size() > 0) {
TVariable* userTypeDef = new TVariable(&structName, type, true);
if (! parseContext.symbolTable.insert(*userTypeDef))
parseContext.error(token.loc, "redefinition", structName.c_str(), "struct");
}
parseContext.declareStruct(token.loc, structName, type);
return true;
}

View File

@@ -161,7 +161,7 @@ bool HlslParseContext::shouldConvertLValue(const TIntermNode* node) const
void HlslParseContext::growGlobalUniformBlock(TSourceLoc& loc, TType& memberType, TString& memberName)
{
makeTypeNonIo(&memberType); //?? losing offsets is okay?
memberType.getQualifier().makeNonIo(); //?? losing offsets is okay?
TParseContextBase::growGlobalUniformBlock(loc, memberType, memberName);
}
@@ -1583,8 +1583,6 @@ TIntermNode* HlslParseContext::transformEntryPoint(const TSourceLoc& loc, TFunct
TVector<TVariable*> inputs;
TVector<TVariable*> outputs;
remapEntryPointIO(userFunction, entryPointOutput, inputs, outputs);
// Once the parameters are moved to shader I/O, they should be non-I/O
remapNonEntryPointIO(userFunction);
// Further this return/in/out transform by flattening, splitting, and assigning locations
const auto makeVariableInOut = [&](TVariable& variable) {
@@ -1689,54 +1687,67 @@ void HlslParseContext::handleFunctionBody(const TSourceLoc& loc, TFunction& func
// AST I/O is done through shader globals declared in the 'in' or 'out'
// storage class. An HLSL entry point has a return value, input parameters
// and output parameters. These need to get remapped to the AST I/O.
void HlslParseContext::remapEntryPointIO(const TFunction& function, TVariable*& returnValue,
void HlslParseContext::remapEntryPointIO(TFunction& function, TVariable*& returnValue,
TVector<TVariable*>& inputs, TVector<TVariable*>& outputs)
{
const auto remapType = [&](TType& type) {
const auto remapBuiltInType = [&](TType& type) {
switch (type.getQualifier().builtIn) {
case EbvFragDepthGreater:
intermediate.setDepth(EldGreater);
type.getQualifier().builtIn = EbvFragDepth;
break;
case EbvFragDepthLesser:
intermediate.setDepth(EldLess);
type.getQualifier().builtIn = EbvFragDepth;
break;
default:
break;
const auto makeIoVariable = [this](const char* name, TType& type) {
const auto remapType = [&](TType& type) {
const auto remapBuiltInType = [&](TType& type) {
switch (type.getQualifier().builtIn) {
case EbvFragDepthGreater:
intermediate.setDepth(EldGreater);
type.getQualifier().builtIn = EbvFragDepth;
break;
case EbvFragDepthLesser:
intermediate.setDepth(EldLess);
type.getQualifier().builtIn = EbvFragDepth;
break;
default:
break;
}
};
remapBuiltInType(type);
if (type.isStruct()) {
auto& members = *type.getStruct();
for (auto member = members.begin(); member != members.end(); ++member)
remapBuiltInType(*member->type); // TODO: lack-of-recursion structure depth problem
}
};
remapBuiltInType(type);
if (type.isStruct()) {
auto members = *type.getStruct();
for (auto member = members.begin(); member != members.end(); ++member)
remapBuiltInType(*member->type); // TODO: lack-of-recursion structure depth problem
TVariable* ioVariable = makeInternalVariable(name, type);
// We might have already lost the IO-aspect of the deep parts of this type,
// get them back and also make them if that hadn't been done yet.
// (The shallow part of IO is already safely copied into the return value.)
type.getQualifier().makeNonIo();
if (type.getStruct() != nullptr) {
auto newList = ioTypeMap.find(ioVariable->getType().getStruct());
if (newList != ioTypeMap.end())
ioVariable->getWritableType().setStruct(newList->second);
}
remapType(ioVariable->getWritableType());
return ioVariable;
};
// return value is actually a shader-scoped output (out)
if (function.getType().getBasicType() == EbtVoid)
returnValue = nullptr;
else {
returnValue = makeInternalVariable("@entryPointOutput", function.getType());
returnValue = makeIoVariable("@entryPointOutput", function.getWritableType());
returnValue->getWritableType().getQualifier().storage = EvqVaryingOut;
remapType(returnValue->getWritableType());
}
// parameters are actually shader-scoped inputs and outputs (in or out)
for (int i = 0; i < function.getParamCount(); i++) {
TType& paramType = *function[i].type;
if (paramType.getQualifier().isParamInput()) {
TVariable* argAsGlobal = makeInternalVariable(*function[i].name, paramType);
TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType);
argAsGlobal->getWritableType().getQualifier().storage = EvqVaryingIn;
inputs.push_back(argAsGlobal);
}
if (paramType.getQualifier().isParamOutput()) {
TVariable* argAsGlobal = makeInternalVariable(*function[i].name, paramType);
TVariable* argAsGlobal = makeIoVariable(function[i].name->c_str(), paramType);
argAsGlobal->getWritableType().getQualifier().storage = EvqVaryingOut;
outputs.push_back(argAsGlobal);
remapType(argAsGlobal->getWritableType());
}
}
}
@@ -1747,11 +1758,11 @@ void HlslParseContext::remapNonEntryPointIO(TFunction& function)
{
// return value
if (function.getType().getBasicType() != EbtVoid)
makeTypeNonIo(&function.getWritableType());
function.getWritableType().getQualifier().makeNonIo();
// parameters
for (int i = 0; i < function.getParamCount(); i++)
makeTypeNonIo(function[i].type);
function[i].type->getQualifier().makeNonIo();
}
// Handle function returns, including type conversions to the function return type
@@ -5349,41 +5360,59 @@ void HlslParseContext::declareTypedef(const TSourceLoc& loc, TString& identifier
error(loc, "name already defined", "typedef", identifier.c_str());
}
// Create a non-IO type from an IO type. If there is no IO data,
// the input type is unmodified. Otherwise, it modifies the type
// in place.
void HlslParseContext::makeTypeNonIo(TType* type)
// Do everything necessary to handle a struct declaration, including
// making IO aliases because HLSL allows mixed IO in a struct that specializes
// based on the usage (input, output, uniform, none).
void HlslParseContext::declareStruct(const TSourceLoc& loc, TString& structName, TType& type)
{
// early out if there's nothing to do: prevents introduction of unneeded types.
if (!type->hasIoData())
// If it was named, which means the type can be reused later, add
// it to the symbol table. (Unless it's a block, in which
// case the name is not a type.)
if (type.getBasicType() == EbtBlock || structName.size() == 0)
return;
type->getQualifier().makeNonIo(); // Sanitize the qualifier.
TVariable* userTypeDef = new TVariable(&structName, type, true);
if (! symbolTable.insert(*userTypeDef)) {
error(loc, "redefinition", structName.c_str(), "struct");
return;
}
// Nothing more to do if there is no deep structure.
if (!type->isStruct())
// See if we need IO aliases for the structure typeList
bool hasIo = false;
for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
if (member->type->getQualifier().hasIoData()) {
hasIo = true;
break;
}
if (member->type->isStruct()) {
if (ioTypeMap.find(member->type->getStruct()) != ioTypeMap.end()) {
hasIo = true;
break;
}
}
}
if (!hasIo)
return;
const auto typeIter = nonIoTypeMap.find(type->getStruct());
// We have IO involved.
if (typeIter != nonIoTypeMap.end()) {
// reuse deep structure if we have sanitized it before, but we must preserve
// our unique shallow structure, which may not be shared with other users of
// the deep copy. Create a new type with the sanitized qualifier, and the
// shared deep structure
type->setStruct(typeIter->second); // share already sanitized deep structure.
} else {
// The type contains interstage IO, but we've never encountered it before.
// Copy it, scrub data we don't want for an non-IO type, and remember it in the nonIoTypeMap
TType nonIoType;
nonIoType.deepCopy(*type);
nonIoType.makeNonIo();
// remember the new deep structure in a map, so we can share it in the future.
nonIoTypeMap[type->getStruct()] = nonIoType.getWritableStruct();
type->shallowCopy(nonIoType); // we modify the input type in place
}
// Make a pure typeList for the symbol table, and cache side copies of IO versions.
TTypeList* newList = new TTypeList;
for (auto member = type.getStruct()->begin(); member != type.getStruct()->end(); ++member) {
TType* memberType = new TType;
memberType->shallowCopy(*member->type);
TTypeLoc newMember = { memberType, member->loc };
if (member->type->isStruct()) {
// swap in an IO child if there is one
auto it = ioTypeMap.find(member->type->getStruct());
if (it != ioTypeMap.end())
newMember.type->setStruct(it->second);
}
newList->push_back(newMember);
member->type->getQualifier().makeNonIo();
}
ioTypeMap[type.getStruct()] = newList;
}
//
@@ -5416,7 +5445,7 @@ TIntermNode* HlslParseContext::declareVariable(const TSourceLoc& loc, TString& i
switch (type.getQualifier().storage) {
case EvqGlobal:
case EvqTemporary:
makeTypeNonIo(&type);
type.getQualifier().makeNonIo();
default:
break;
}

View File

@@ -75,7 +75,7 @@ public:
TIntermAggregate* handleFunctionDefinition(const TSourceLoc&, TFunction&, const TAttributeMap&, TIntermNode*& entryPointTree);
TIntermNode* transformEntryPoint(const TSourceLoc&, TFunction&, const TAttributeMap&);
void handleFunctionBody(const TSourceLoc&, TFunction&, TIntermNode* functionBody, TIntermNode*& node);
void remapEntryPointIO(const TFunction& function, TVariable*& returnValue, TVector<TVariable*>& inputs, TVector<TVariable*>& outputs);
void remapEntryPointIO(TFunction& function, TVariable*& returnValue, TVector<TVariable*>& inputs, TVector<TVariable*>& outputs);
void remapNonEntryPointIO(TFunction& function);
TIntermNode* handleReturnValue(const TSourceLoc&, TIntermTyped*);
void handleFunctionArgument(TFunction*, TIntermTyped*& arguments, TIntermTyped* newArg);
@@ -131,6 +131,7 @@ public:
const TFunction* findFunction(const TSourceLoc& loc, TFunction& call, bool& builtIn, TIntermTyped*& args);
void declareTypedef(const TSourceLoc&, TString& identifier, const TType&, TArraySizes* typeArray = 0);
void declareStruct(const TSourceLoc&, TString& structName, TType&);
TIntermNode* declareVariable(const TSourceLoc&, TString& identifier, TType&, TIntermTyped* initializer = 0);
void lengthenList(const TSourceLoc&, TIntermSequence& list, int size);
TIntermTyped* addConstructor(const TSourceLoc&, TIntermNode*, const TType&);
@@ -230,10 +231,6 @@ protected:
int flattenStruct(const TSourceLoc& loc, const TVariable& variable, const TType&, TFlattenData&, TString name);
int flattenArray(const TSourceLoc& loc, const TVariable& variable, const TType&, TFlattenData&, TString name);
// Create a non-IO type from an IO type. If there is no IO data, this returns the input type unmodified.
// Otherwise, it modifies the type in place, and returns a pointer to it.
void makeTypeNonIo(TType*);
void finish() override; // post-processing
// Current state of parsing
@@ -299,9 +296,8 @@ protected:
TVector<int> flattenLevel; // nested postfix operator level for flattening
TVector<int> flattenOffset; // cumulative offset for flattening
// Sanitized type map. If the same type is sanitized again, we want to reuse it.
// We cannot index by the TType: the map is typelist to typelist.
TMap<const TTypeList*, TTypeList*> nonIoTypeMap;
// IO-type map.
TMap<const TTypeList*, TTypeList*> ioTypeMap;
// Structure splitting data:
TMap<int, TVariable*> splitIoVars; // variables with the builtin interstage IO removed, indexed by unique ID.