diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-03-01 09:43:47 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-03-01 09:43:47 -0800 |
| commit | 620af1c60a2e84bbbc0e74f11cb9bc6a6976d9e4 (patch) | |
| tree | bba1e1d60c950f71506dd235ecc3309e6aa4880f /source | |
| parent | efca2bb38700847adb2385d311b8b801376659bb (diff) | |
Move enumeration of shader parameters to Program/EntryPoint (#870)
There's a certain amount of logic in `parameter-binding.cpp` that just has to do with the basic problem of enumerating the shader parameters of a `Program`. The main source of complexity is that for legacy/compatibility reasons we need to consider two shader parameters with the same name as being the "same" parameter for layout purposes, and then we need to do a bunch of validation to ensure that these parameters have compatible types.
The biggest part of this change is moving that logic to `Program`, so that it builds up a list of its shader parameters during the front-end work, so that any errors related to bad redeclarations will now come up even if we aren't generated target-specific layouts/code.
All of the code for `getReflectionName`, `StructuralTypeMatchStack`, etc. is pretty much copy-pasted from `parameter-binding.cpp` over to `check.cpp`, with the `ParameterBindingContext` replaced with a `DiagnosticSink`.
The `Program::_collectShaderParameters` function (renamed from `_collectExistentialParams`) then deals with the enumeration and deduplication logic that used to happen in `collectGlobalScopeParameters()`.
The new declarations in `compiler.h` reveal the underlying reason for this change: by letting `Program` and `EntryPoint` handle the canonical enumeration of parameters, we can associate each parameter with the range of existential type slots it uses, which will simplify certain work around interfaces (not in this change...).
Moving the code out of parameter binding and into `check.cpp` revealed some unused GLSL-related code that I removed while I was at it.
I also found that the `IsDeclaration` case of `VarLayoutFlag` wasn't actually being used, so I went ahead and removed it (we can easily re-add it if we ever find a need for it).
Overall this isn't a big cleanup (mostly just code moving, rather than being eliminated), but it will facilitate other changes, and it seems cleaner overall to do this work once in target-independent logic, rather than per-target.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 515 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 2 | ||||
| -rw-r--r-- | source/slang/compiler.h | 30 | ||||
| -rw-r--r-- | source/slang/parameter-binding.cpp | 644 | ||||
| -rw-r--r-- | source/slang/syntax.h | 14 | ||||
| -rw-r--r-- | source/slang/type-layout.h | 1 |
6 files changed, 608 insertions, 598 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index a9f84c5c3..998324612 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -9306,7 +9306,7 @@ namespace Slang } /// Recursively walk `paramDeclRef` and add any required existential slots to `ioSlots`. - static void _collectExistentialParamsRec( + static void _collectExistentialSlotsRec( ExistentialSlots& ioSlots, DeclRef<VarDeclBase> paramDeclRef) { @@ -9339,7 +9339,7 @@ namespace Slang if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()) continue; - _collectExistentialParamsRec(ioSlots, fieldDeclRef); + _collectExistentialSlotsRec(ioSlots, fieldDeclRef); } } } @@ -9349,11 +9349,26 @@ namespace Slang // element types. } + /// Add information about a shader parameter to `ioParams` and `ioSlots` + static void _collectExistentialSlotsForShaderParam( + ShaderParamInfo& ioParamInfo, + ExistentialSlots& ioSlots, + DeclRef<VarDeclBase> paramDeclRef) + { + UInt startSlot = ioSlots.types.Count(); + _collectExistentialSlotsRec(ioSlots, paramDeclRef); + UInt endSlot = ioSlots.types.Count(); + UInt slotCount = endSlot - startSlot; + + ioParamInfo.firstExistentialTypeSlot = startSlot; + ioParamInfo.existentialTypeSlotCount = slotCount; + } + /// Enumerate the existential-type parameters of an `EntryPoint`. /// /// Any parameters found will be added to the list of existential slots on `this`. /// - void EntryPoint::_collectExistentialParams() + void EntryPoint::_collectShaderParams() { // Note: we defensively test whether there is a function decl-ref // because this routine gets called from the constructor, and @@ -9363,7 +9378,15 @@ namespace Slang { for( auto paramDeclRef : GetParameters(funcDeclRef) ) { - _collectExistentialParamsRec(m_existentialSlots, paramDeclRef); + ShaderParamInfo shaderParamInfo; + shaderParamInfo.paramDeclRef = paramDeclRef; + + _collectExistentialSlotsForShaderParam( + shaderParamInfo, + m_existentialSlots, + paramDeclRef); + + m_shaderParams.Add(shaderParamInfo); } } } @@ -9644,16 +9667,433 @@ namespace Slang return entryPoint; } + /// Get the name a variable will use for reflection purposes +Name* getReflectionName(VarDeclBase* varDecl) +{ + if (auto reflectionNameModifier = varDecl->FindModifier<ParameterGroupReflectionName>()) + return reflectionNameModifier->nameAndLoc.name; + + return varDecl->getName(); +} + +// Information tracked when doing a structural +// match of types. +struct StructuralTypeMatchStack +{ + DeclRef<VarDeclBase> leftDecl; + DeclRef<VarDeclBase> rightDecl; + StructuralTypeMatchStack* parent; +}; + +static void diagnoseParameterTypeMismatch( + DiagnosticSink* sink, + StructuralTypeMatchStack* inStack) +{ + SLANG_ASSERT(inStack); + + // The bottom-most entry in the stack should represent + // the shader parameters that kicked things off + auto stack = inStack; + while(stack->parent) + stack = stack->parent; + + sink->diagnose(stack->leftDecl, Diagnostics::shaderParameterDeclarationsDontMatch, getReflectionName(stack->leftDecl)); + sink->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl)); +} + +// Two types that were expected to match did not. +// Inform the user with a suitable message. +static void diagnoseTypeMismatch( + DiagnosticSink* sink, + StructuralTypeMatchStack* inStack) +{ + auto stack = inStack; + SLANG_ASSERT(stack); + diagnoseParameterTypeMismatch(sink, stack); + + auto leftType = GetType(stack->leftDecl); + auto rightType = GetType(stack->rightDecl); + + if( stack->parent ) + { + sink->diagnose(stack->leftDecl, Diagnostics::fieldTypeMisMatch, getReflectionName(stack->leftDecl), leftType, rightType); + sink->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl)); + + stack = stack->parent; + if( stack ) + { + while( stack->parent ) + { + sink->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl)); + stack = stack->parent; + } + } + } + else + { + sink->diagnose(stack->leftDecl, Diagnostics::shaderParameterTypeMismatch, leftType, rightType); + } +} + +// Two types that were expected to match did not. +// Inform the user with a suitable message. +static void diagnoseTypeFieldsMismatch( + DiagnosticSink* sink, + DeclRef<Decl> const& left, + DeclRef<Decl> const& right, + StructuralTypeMatchStack* stack) +{ + diagnoseParameterTypeMismatch(sink, stack); + + sink->diagnose(left, Diagnostics::fieldDeclarationsDontMatch, left.GetName()); + sink->diagnose(right, Diagnostics::seeOtherDeclarationOf, right.GetName()); + + if( stack ) + { + while( stack->parent ) + { + sink->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl)); + stack = stack->parent; + } + } +} + +static void collectFields( + DeclRef<AggTypeDecl> declRef, + List<DeclRef<VarDecl>>& outFields) +{ + for( auto fieldDeclRef : getMembersOfType<VarDecl>(declRef) ) + { + if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()) + continue; + + outFields.Add(fieldDeclRef); + } +} + +static bool validateTypesMatch( + DiagnosticSink* sink, + Type* left, + Type* right, + StructuralTypeMatchStack* stack); + +static bool validateIntValuesMatch( + DiagnosticSink* sink, + IntVal* left, + IntVal* right, + StructuralTypeMatchStack* stack) +{ + if(left->EqualsVal(right)) + return true; + + // TODO: are there other cases we need to handle here? + + diagnoseTypeMismatch(sink, stack); + return false; +} + + +static bool validateValuesMatch( + DiagnosticSink* sink, + Val* left, + Val* right, + StructuralTypeMatchStack* stack) +{ + if( auto leftType = dynamicCast<Type>(left) ) + { + if( auto rightType = dynamicCast<Type>(right) ) + { + return validateTypesMatch(sink, leftType, rightType, stack); + } + } + + if( auto leftInt = dynamicCast<IntVal>(left) ) + { + if( auto rightInt = dynamicCast<IntVal>(right) ) + { + return validateIntValuesMatch(sink, leftInt, rightInt, stack); + } + } + + if( auto leftWitness = dynamicCast<SubtypeWitness>(left) ) + { + if( auto rightWitness = dynamicCast<SubtypeWitness>(right) ) + { + return true; + } + } + + diagnoseTypeMismatch(sink, stack); + return false; +} + +static bool validateGenericSubstitutionsMatch( + DiagnosticSink* sink, + GenericSubstitution* left, + GenericSubstitution* right, + StructuralTypeMatchStack* stack) +{ + if( !left ) + { + if( !right ) + { + return true; + } + + diagnoseTypeMismatch(sink, stack); + return false; + } + + + + UInt argCount = left->args.Count(); + if( argCount != right->args.Count() ) + { + diagnoseTypeMismatch(sink, stack); + return false; + } + + for( UInt aa = 0; aa < argCount; ++aa ) + { + auto leftArg = left->args[aa]; + auto rightArg = right->args[aa]; + + if(!validateValuesMatch(sink, leftArg, rightArg, stack)) + return false; + } + + return true; +} + +static bool validateThisTypeSubstitutionsMatch( + DiagnosticSink* /*sink*/, + ThisTypeSubstitution* /*left*/, + ThisTypeSubstitution* /*right*/, + StructuralTypeMatchStack* /*stack*/) +{ + // TODO: actual checking. + return true; +} + +static bool validateSpecializationsMatch( + DiagnosticSink* sink, + SubstitutionSet left, + SubstitutionSet right, + StructuralTypeMatchStack* stack) +{ + auto ll = left.substitutions; + auto rr = right.substitutions; + for(;;) + { + // Skip any global generic substitutions. + if(auto leftGlobalGeneric = as<GlobalGenericParamSubstitution>(ll)) + { + ll = leftGlobalGeneric->outer; + continue; + } + if(auto rightGlobalGeneric = as<GlobalGenericParamSubstitution>(rr)) + { + rr = rightGlobalGeneric->outer; + continue; + } + + // If either ran out, then we expect both to have run out. + if(!ll || !rr) + return !ll && !rr; + + auto leftSubst = ll; + auto rightSubst = rr; + + ll = ll->outer; + rr = rr->outer; + + if(auto leftGeneric = as<GenericSubstitution>(leftSubst)) + { + if(auto rightGeneric = as<GenericSubstitution>(rightSubst)) + { + if(validateGenericSubstitutionsMatch(sink, leftGeneric, rightGeneric, stack)) + { + continue; + } + } + } + else if(auto leftThisType = as<ThisTypeSubstitution>(leftSubst)) + { + if(auto rightThisType = as<ThisTypeSubstitution>(rightSubst)) + { + if(validateThisTypeSubstitutionsMatch(sink, leftThisType, rightThisType, stack)) + { + continue; + } + } + } + + return false; + } + + return true; +} + +// Determine if two types "match" for the purposes of `cbuffer` layout rules. +// +static bool validateTypesMatch( + DiagnosticSink* sink, + Type* left, + Type* right, + StructuralTypeMatchStack* stack) +{ + if(left->Equals(right)) + return true; + + // It is possible that the types don't match exactly, but + // they *do* match structurally. + + // Note: the following code will lead to infinite recursion if there + // are ever recursive types. We'd need a more refined system to + // cache the matches we've already found. + + if( auto leftDeclRefType = as<DeclRefType>(left) ) + { + if( auto rightDeclRefType = as<DeclRefType>(right) ) + { + // Are they references to matching decl refs? + auto leftDeclRef = leftDeclRefType->declRef; + auto rightDeclRef = rightDeclRefType->declRef; + + // Do the reference the same declaration? Or declarations + // with the same name? + // + // TODO: we should only consider the same-name case if the + // declarations come from translation units being compiled + // (and not an imported module). + if( leftDeclRef.getDecl() == rightDeclRef.getDecl() + || leftDeclRef.GetName() == rightDeclRef.GetName() ) + { + // Check that any generic arguments match + if( !validateSpecializationsMatch( + sink, + leftDeclRef.substitutions, + rightDeclRef.substitutions, + stack) ) + { + return false; + } + + // Check that any declared fields match too. + if( auto leftStructDeclRef = leftDeclRef.as<AggTypeDecl>() ) + { + if( auto rightStructDeclRef = rightDeclRef.as<AggTypeDecl>() ) + { + List<DeclRef<VarDecl>> leftFields; + List<DeclRef<VarDecl>> rightFields; + + collectFields(leftStructDeclRef, leftFields); + collectFields(rightStructDeclRef, rightFields); + + UInt leftFieldCount = leftFields.Count(); + UInt rightFieldCount = rightFields.Count(); + + if( leftFieldCount != rightFieldCount ) + { + diagnoseTypeFieldsMismatch(sink, leftDeclRef, rightDeclRef, stack); + return false; + } + + for( UInt ii = 0; ii < leftFieldCount; ++ii ) + { + auto leftField = leftFields[ii]; + auto rightField = rightFields[ii]; + + if( leftField.GetName() != rightField.GetName() ) + { + diagnoseTypeFieldsMismatch(sink, leftDeclRef, rightDeclRef, stack); + return false; + } + + auto leftFieldType = GetType(leftField); + auto rightFieldType = GetType(rightField); + + StructuralTypeMatchStack subStack; + subStack.parent = stack; + subStack.leftDecl = leftField; + subStack.rightDecl = rightField; + + if(!validateTypesMatch(sink, leftFieldType,rightFieldType, &subStack)) + return false; + } + } + } + + // Everything seemed to match recursively. + return true; + } + } + } + + // If we are looking at `T[N]` and `U[M]` we want to check that + // `T` is structurally equivalent to `U` and `N` is the same as `M`. + else if( auto leftArrayType = as<ArrayExpressionType>(left) ) + { + if( auto rightArrayType = as<ArrayExpressionType>(right) ) + { + if(!validateTypesMatch(sink, leftArrayType->baseType, rightArrayType->baseType, stack) ) + return false; + + if(!validateValuesMatch(sink, leftArrayType->ArrayLength, rightArrayType->ArrayLength, stack)) + return false; + + return true; + } + } + + diagnoseTypeMismatch(sink, stack); + return false; +} + +// This function is supposed to determine if two global shader +// parameter declarations represent the same logical parameter +// (so that they should get the exact same binding(s) allocated). +// +static bool doesParameterMatch( + DiagnosticSink* sink, + DeclRef<VarDeclBase> varDeclRef, + DeclRef<VarDeclBase> existingVarDeclRef) +{ + StructuralTypeMatchStack stack; + stack.parent = nullptr; + stack.leftDecl = varDeclRef; + stack.rightDecl = existingVarDeclRef; + + validateTypesMatch(sink, GetType(varDeclRef), GetType(existingVarDeclRef), &stack); + + return true; +} + + + + /// Enumerate the existential-type parameters of a `Program`. /// /// Any parameters found will be added to the list of existential slots on `this`. /// - void Program::_collectExistentialParams() + void Program::_collectShaderParams(DiagnosticSink* sink) { - // We need to inspect all of the global shader parameters + // We need to collect all of the global shader parameters // referenced by the compile request, and for each we - // need to determine what existential types parameters it implies. + // need to do a few things: + // + // * We need to determine if the parameter is a duplicate/redeclaration + // of the "same" parameter in another translation unit, and collapse + // those into one logical shader parameter if so. + // + // * We need to determine what existential type slots are introduced + // by the parameter, and associate that information with the parameter. + // + // To deal with the first issue, we will maintain a map from a parameter + // name to the index of an existing parameter with that name. // + Dictionary<Name*, Int> mapNameToParamIndex; + for( auto module : getModuleDependencies() ) { auto moduleDecl = module->getModuleDecl(); @@ -9662,9 +10102,55 @@ namespace Slang if(!isGlobalShaderParameter(globalVar)) continue; - _collectExistentialParamsRec( + // This declaration may represent the same logical parameter + // as a declaration that came from a different translation unit. + // If that is the case, we want to re-use the same `ShaderParamInfo` + // across both parameters. + // + // TODO: This logic currently detects *any* global-scope parameters + // with matching names, but it should eventually be narrowly + // scoped so that it only applies to parameters from unnamed modules + // (that is, modules that represent directly-compiled shader files + // and not `import`ed code). + // + // First we look for an existing entry matching the name + // of this parameter: + // + auto paramName = getReflectionName(globalVar); + Int existingParamIndex = -1; + if( mapNameToParamIndex.TryGetValue(paramName, existingParamIndex) ) + { + // If the parameters have the same name, but don't "match" according to some reasonable rules, + // then we will treat them as distinct global parameters. + // + // Note: all of the mismatch cases currently report errors, so that + // compilation will fail on a mismatch. + // + auto& existingParam = m_shaderParams[existingParamIndex]; + if( doesParameterMatch(sink, makeDeclRef(globalVar.Ptr()), existingParam.paramDeclRef) ) + { + // If we hit this case, then we had a match, and we should + // consider the new variable to be a redclaration of + // the existing one. + + existingParam.additionalParamDeclRefs.Add( + makeDeclRef(globalVar.Ptr())); + continue; + } + } + + Int newParamIndex = Int(m_shaderParams.Count()); + mapNameToParamIndex.Add(paramName, newParamIndex); + + GlobalShaderParamInfo shaderParamInfo; + shaderParamInfo.paramDeclRef = makeDeclRef(globalVar.Ptr()); + + _collectExistentialSlotsForShaderParam( + shaderParamInfo, m_globalExistentialSlots, makeDeclRef(globalVar.Ptr())); + + m_shaderParams.Add(shaderParamInfo); } } } @@ -9795,7 +10281,7 @@ namespace Slang } } - program->_collectExistentialParams(); + program->_collectShaderParams(sink); return program; } @@ -10181,8 +10667,15 @@ namespace Slang specializedProgram->setGlobalGenericSubsitution(globalGenericSubsts); - // Now deal with the existential arguments - specializedProgram->_collectExistentialParams(); + // Now deal with the shader parameters and existential arguments + // + // Note: We should in theory be able to just copy over the shader + // parameters and existential slot information from the unspecialized + // program. This could save some time, but it would also mean that + // the only way to create a specialized program is by creating an + // unspecialized on first, which is maybe not always desirable. + // + specializedProgram->_collectShaderParams(sink); specializedProgram->_specializeExistentialSlots(globalExistentialArgs, sink); return specializedProgram; diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 9037c0944..40bc83052 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -229,7 +229,7 @@ namespace Slang // Collect any existential-type parameters used by the entry point // - _collectExistentialParams(); + _collectShaderParams(); } Module* EntryPoint::getModule() diff --git a/source/slang/compiler.h b/source/slang/compiler.h index d3b872054..843cae1b3 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -137,6 +137,22 @@ namespace Slang List<Arg> args; }; + /// Information collected about global or entry-point shader parameters + struct ShaderParamInfo + { + DeclRef<VarDeclBase> paramDeclRef; + UInt firstExistentialTypeSlot = 0; + UInt existentialTypeSlotCount = 0; + }; + + /// Extended information specific to global shader parameters + struct GlobalShaderParamInfo : ShaderParamInfo + { + // Additional global-scope declarations that are conceptually + // declaring the "same" parameter as the `paramDeclRef`. + List<DeclRef<VarDeclBase>> additionalParamDeclRefs; + }; + /// A request for the front-end to find and validate an entry-point function struct FrontEndEntryPointRequest : RefObject { @@ -287,6 +303,8 @@ namespace Slang Type* getExistentialSlotType(UInt index) { return m_existentialSlots.types[index]; } ExistentialSlots::Arg getExistentialSlotArg(UInt index) { return m_existentialSlots.args[index]; } + List<ShaderParamInfo> const& getShaderParams() { return m_shaderParams; } + void _specializeExistentialSlots( List<RefPtr<Expr>> const& args, DiagnosticSink* sink); @@ -297,7 +315,7 @@ namespace Slang Profile profile, DeclRef<FuncDecl> funcDeclRef); - void _collectExistentialParams(); + void _collectShaderParams(); // The name of the entry point function (e.g., `main`) // @@ -310,6 +328,9 @@ namespace Slang /// The existential/interface slots associated with the entry point parameter scope. ExistentialSlots m_existentialSlots; + /// Information about entry-point parameters + List<ShaderParamInfo> m_shaderParams; + // The profile that the entry point will be compiled for // (this is a combination of the target stage, and also // a feature level that sets capabilities) @@ -920,7 +941,9 @@ namespace Slang Type* getExistentialSlotType(UInt index) { return m_globalExistentialSlots.types[index]; } ExistentialSlots::Arg getExistentialSlotArg(UInt index) { return m_globalExistentialSlots.args[index]; } - void _collectExistentialParams(); + List<GlobalShaderParamInfo> const& getShaderParams() { return m_shaderParams; } + + void _collectShaderParams(DiagnosticSink* sink); void _specializeExistentialSlots( List<RefPtr<Expr>> const& args, DiagnosticSink* sink); @@ -949,6 +972,9 @@ namespace Slang // The existential/interface slots associated with the global scope. ExistentialSlots m_globalExistentialSlots; + /// Information about global shader parameters + List<GlobalShaderParamInfo> m_shaderParams; + // Generated IR for this program. RefPtr<IRModule> m_irModule; diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp index dda3a8621..a472a9280 100644 --- a/source/slang/parameter-binding.cpp +++ b/source/slang/parameter-binding.cpp @@ -395,9 +395,6 @@ struct ParameterBindingContext // the resource usage of shader parameters. TypeLayoutContext layoutContext; - // A dictionary to accelerate looking up parameters by name - Dictionary<Name*, ParameterInfo*> mapNameToParameterInfo; - // What stage (if any) are we compiling for? Stage stage; @@ -573,424 +570,6 @@ LayoutSemanticInfo ExtractLayoutSemanticInfo( return info; } -static Name* getReflectionName(VarDeclBase* varDecl) -{ - if (auto reflectionNameModifier = varDecl->FindModifier<ParameterGroupReflectionName>()) - return reflectionNameModifier->nameAndLoc.name; - - return varDecl->getName(); -} - -// Information tracked when doing a structural -// match of types. -struct StructuralTypeMatchStack -{ - DeclRef<VarDeclBase> leftDecl; - DeclRef<VarDeclBase> rightDecl; - StructuralTypeMatchStack* parent; -}; - -static void diagnoseParameterTypeMismatch( - ParameterBindingContext* context, - StructuralTypeMatchStack* inStack) -{ - SLANG_ASSERT(inStack); - - // The bottom-most entry in the stack should represent - // the shader parameters that kicked things off - auto stack = inStack; - while(stack->parent) - stack = stack->parent; - - getSink(context)->diagnose(stack->leftDecl, Diagnostics::shaderParameterDeclarationsDontMatch, getReflectionName(stack->leftDecl)); - getSink(context)->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl)); -} - -// Two types that were expected to match did not. -// Inform the user with a suitable message. -static void diagnoseTypeMismatch( - ParameterBindingContext* context, - StructuralTypeMatchStack* inStack) -{ - auto stack = inStack; - SLANG_ASSERT(stack); - diagnoseParameterTypeMismatch(context, stack); - - auto leftType = GetType(stack->leftDecl); - auto rightType = GetType(stack->rightDecl); - - if( stack->parent ) - { - getSink(context)->diagnose(stack->leftDecl, Diagnostics::fieldTypeMisMatch, getReflectionName(stack->leftDecl), leftType, rightType); - getSink(context)->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl)); - - stack = stack->parent; - if( stack ) - { - while( stack->parent ) - { - getSink(context)->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl)); - stack = stack->parent; - } - } - } - else - { - getSink(context)->diagnose(stack->leftDecl, Diagnostics::shaderParameterTypeMismatch, leftType, rightType); - } -} - -// Two types that were expected to match did not. -// Inform the user with a suitable message. -static void diagnoseTypeFieldsMismatch( - ParameterBindingContext* context, - DeclRef<Decl> const& left, - DeclRef<Decl> const& right, - StructuralTypeMatchStack* stack) -{ - diagnoseParameterTypeMismatch(context, stack); - - getSink(context)->diagnose(left, Diagnostics::fieldDeclarationsDontMatch, left.GetName()); - getSink(context)->diagnose(right, Diagnostics::seeOtherDeclarationOf, right.GetName()); - - if( stack ) - { - while( stack->parent ) - { - getSink(context)->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl)); - stack = stack->parent; - } - } -} - -static void collectFields( - DeclRef<AggTypeDecl> declRef, - List<DeclRef<VarDecl>>& outFields) -{ - for( auto fieldDeclRef : getMembersOfType<VarDecl>(declRef) ) - { - if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>()) - continue; - - outFields.Add(fieldDeclRef); - } -} - -static bool validateTypesMatch( - ParameterBindingContext* context, - Type* left, - Type* right, - StructuralTypeMatchStack* stack); - -static bool validateIntValuesMatch( - ParameterBindingContext* context, - IntVal* left, - IntVal* right, - StructuralTypeMatchStack* stack) -{ - if(left->EqualsVal(right)) - return true; - - // TODO: are there other cases we need to handle here? - - diagnoseTypeMismatch(context, stack); - return false; -} - - -static bool validateValuesMatch( - ParameterBindingContext* context, - Val* left, - Val* right, - StructuralTypeMatchStack* stack) -{ - if( auto leftType = dynamicCast<Type>(left) ) - { - if( auto rightType = dynamicCast<Type>(right) ) - { - return validateTypesMatch(context, leftType, rightType, stack); - } - } - - if( auto leftInt = dynamicCast<IntVal>(left) ) - { - if( auto rightInt = dynamicCast<IntVal>(right) ) - { - return validateIntValuesMatch(context, leftInt, rightInt, stack); - } - } - - if( auto leftWitness = dynamicCast<SubtypeWitness>(left) ) - { - if( auto rightWitness = dynamicCast<SubtypeWitness>(right) ) - { - return true; - } - } - - diagnoseTypeMismatch(context, stack); - return false; -} - -static bool validateGenericSubstitutionsMatch( - ParameterBindingContext* context, - GenericSubstitution* left, - GenericSubstitution* right, - StructuralTypeMatchStack* stack) -{ - if( !left ) - { - if( !right ) - { - return true; - } - - diagnoseTypeMismatch(context, stack); - return false; - } - - - - UInt argCount = left->args.Count(); - if( argCount != right->args.Count() ) - { - diagnoseTypeMismatch(context, stack); - return false; - } - - for( UInt aa = 0; aa < argCount; ++aa ) - { - auto leftArg = left->args[aa]; - auto rightArg = right->args[aa]; - - if(!validateValuesMatch(context, leftArg, rightArg, stack)) - return false; - } - - return true; -} - -static bool validateThisTypeSubstitutionsMatch( - ParameterBindingContext* /*context*/, - ThisTypeSubstitution* /*left*/, - ThisTypeSubstitution* /*right*/, - StructuralTypeMatchStack* /*stack*/) -{ - // TODO: actual checking. - return true; -} - -static bool validateSpecializationsMatch( - ParameterBindingContext* context, - SubstitutionSet left, - SubstitutionSet right, - StructuralTypeMatchStack* stack) -{ - auto ll = left.substitutions; - auto rr = right.substitutions; - for(;;) - { - // Skip any global generic substitutions. - if(auto leftGlobalGeneric = as<GlobalGenericParamSubstitution>(ll)) - { - ll = leftGlobalGeneric->outer; - continue; - } - if(auto rightGlobalGeneric = as<GlobalGenericParamSubstitution>(rr)) - { - rr = rightGlobalGeneric->outer; - continue; - } - - // If either ran out, then we expect both to have run out. - if(!ll || !rr) - return !ll && !rr; - - auto leftSubst = ll; - auto rightSubst = rr; - - ll = ll->outer; - rr = rr->outer; - - if(auto leftGeneric = as<GenericSubstitution>(leftSubst)) - { - if(auto rightGeneric = as<GenericSubstitution>(rightSubst)) - { - if(validateGenericSubstitutionsMatch(context, leftGeneric, rightGeneric, stack)) - { - continue; - } - } - } - else if(auto leftThisType = as<ThisTypeSubstitution>(leftSubst)) - { - if(auto rightThisType = as<ThisTypeSubstitution>(rightSubst)) - { - if(validateThisTypeSubstitutionsMatch(context, leftThisType, rightThisType, stack)) - { - continue; - } - } - } - - return false; - } - - return true; -} - -// Determine if two types "match" for the purposes of `cbuffer` layout rules. -// -static bool validateTypesMatch( - ParameterBindingContext* context, - Type* left, - Type* right, - StructuralTypeMatchStack* stack) -{ - if(left->Equals(right)) - return true; - - // It is possible that the types don't match exactly, but - // they *do* match structurally. - - // Note: the following code will lead to infinite recursion if there - // are ever recursive types. We'd need a more refined system to - // cache the matches we've already found. - - if( auto leftDeclRefType = as<DeclRefType>(left) ) - { - if( auto rightDeclRefType = as<DeclRefType>(right) ) - { - // Are they references to matching decl refs? - auto leftDeclRef = leftDeclRefType->declRef; - auto rightDeclRef = rightDeclRefType->declRef; - - // Do the reference the same declaration? Or declarations - // with the same name? - // - // TODO: we should only consider the same-name case if the - // declarations come from translation units being compiled - // (and not an imported module). - if( leftDeclRef.getDecl() == rightDeclRef.getDecl() - || leftDeclRef.GetName() == rightDeclRef.GetName() ) - { - // Check that any generic arguments match - if( !validateSpecializationsMatch( - context, - leftDeclRef.substitutions, - rightDeclRef.substitutions, - stack) ) - { - return false; - } - - // Check that any declared fields match too. - if( auto leftStructDeclRef = leftDeclRef.as<AggTypeDecl>() ) - { - if( auto rightStructDeclRef = rightDeclRef.as<AggTypeDecl>() ) - { - List<DeclRef<VarDecl>> leftFields; - List<DeclRef<VarDecl>> rightFields; - - collectFields(leftStructDeclRef, leftFields); - collectFields(rightStructDeclRef, rightFields); - - UInt leftFieldCount = leftFields.Count(); - UInt rightFieldCount = rightFields.Count(); - - if( leftFieldCount != rightFieldCount ) - { - diagnoseTypeFieldsMismatch(context, leftDeclRef, rightDeclRef, stack); - return false; - } - - for( UInt ii = 0; ii < leftFieldCount; ++ii ) - { - auto leftField = leftFields[ii]; - auto rightField = rightFields[ii]; - - if( leftField.GetName() != rightField.GetName() ) - { - diagnoseTypeFieldsMismatch(context, leftDeclRef, rightDeclRef, stack); - return false; - } - - auto leftFieldType = GetType(leftField); - auto rightFieldType = GetType(rightField); - - StructuralTypeMatchStack subStack; - subStack.parent = stack; - subStack.leftDecl = leftField; - subStack.rightDecl = rightField; - - if(!validateTypesMatch(context, leftFieldType,rightFieldType, &subStack)) - return false; - } - } - } - - // Everything seemed to match recursively. - return true; - } - } - } - - // If we are looking at `T[N]` and `U[M]` we want to check that - // `T` is structurally equivalent to `U` and `N` is the same as `M`. - else if( auto leftArrayType = as<ArrayExpressionType>(left) ) - { - if( auto rightArrayType = as<ArrayExpressionType>(right) ) - { - if(!validateTypesMatch(context, leftArrayType->baseType, rightArrayType->baseType, stack) ) - return false; - - if(!validateValuesMatch(context, leftArrayType->ArrayLength, rightArrayType->ArrayLength, stack)) - return false; - - return true; - } - } - - diagnoseTypeMismatch(context, stack); - return false; -} - -// This function is supposed to determine if two global shader -// parameter declarations represent the same logical parameter -// (so that they should get the exact same binding(s) allocated). -// -static bool doesParameterMatch( - ParameterBindingContext* context, - RefPtr<VarLayout> varLayout, - ParameterInfo* parameterInfo) -{ - // Any "varying" parameter should automatically be excluded - // - // Note that we use the `typeLayout` field rather than - // looking at resource information on the variable directly, - // because this may be called when binding hasn't been performed. - for (auto rr : varLayout->typeLayout->resourceInfos) - { - switch (rr.kind) - { - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - return false; - - default: - break; - } - } - - StructuralTypeMatchStack stack; - stack.parent = nullptr; - stack.leftDecl = varLayout->varDecl; - stack.rightDecl = parameterInfo->varLayouts[0]->varDecl; - - validateTypesMatch(context, varLayout->typeLayout->type, parameterInfo->varLayouts[0]->typeLayout->type, &stack); - - return true; -} // @@ -1020,90 +599,6 @@ static bool findLayoutArg( return findLayoutArg<T>(declRef.getDecl(), outVal); } -// - -static bool isGLSLBuiltinName(VarDeclBase* varDecl) -{ - return getText(getReflectionName(varDecl)).StartsWith("gl_"); -} - -RefPtr<Type> tryGetEffectiveTypeForGLSLVaryingInput( - ParameterBindingContext* context, - VarDeclBase* varDecl) -{ - if (isGLSLBuiltinName(varDecl)) - return nullptr; - - auto type = varDecl->getType(); - if( varDecl->HasModifier<InModifier>() || as<GLSLInputParameterGroupType>(type)) - { - // Special case to handle "arrayed" shader inputs, as used - // for Geometry and Hull input - switch( context->stage ) - { - case Stage::Geometry: - case Stage::Hull: - case Stage::Domain: - // Tessellation `patch` variables should stay as written - if( !varDecl->HasModifier<GLSLPatchModifier>() ) - { - // Unwrap array type, if present - if( auto arrayType = as<ArrayExpressionType>(type) ) - { - type = arrayType->baseType.Ptr(); - } - } - break; - - default: - break; - } - - return type; - } - - return nullptr; -} - -RefPtr<Type> tryGetEffectiveTypeForGLSLVaryingOutput( - ParameterBindingContext* context, - VarDeclBase* varDecl) -{ - if (isGLSLBuiltinName(varDecl)) - return nullptr; - - auto type = varDecl->getType(); - if( varDecl->HasModifier<OutModifier>() || as<GLSLOutputParameterGroupType>(type)) - { - // Special case to handle "arrayed" shader outputs, as used - // for Hull Shader output - // - // Note(tfoley): there is unfortunate code duplication - // with the `in` case above. - switch( context->stage ) - { - case Stage::Hull: - // Tessellation `patch` variables should stay as written - if( !varDecl->HasModifier<GLSLPatchModifier>() ) - { - // Unwrap array type, if present - if( auto arrayType = as<ArrayExpressionType>(type) ) - { - type = arrayType->baseType.Ptr(); - } - } - break; - - default: - break; - } - - return type; - } - - return nullptr; -} - /// Determine how to lay out a global variable that might be a shader parameter. /// /// Returns `nullptr` if the declaration does not represent a shader parameter. @@ -1183,19 +678,21 @@ static void collectGlobalGenericParameter( // Collect a single declaration into our set of parameters static void collectGlobalScopeParameter( - ParameterBindingContext* context, - RefPtr<VarDeclBase> varDecl, - SubstitutionSet globalGenericSubst) + ParameterBindingContext* context, + GlobalShaderParamInfo const& shaderParamInfo, + SubstitutionSet globalGenericSubst) { + auto varDeclRef = shaderParamInfo.paramDeclRef; + // We apply any substitutions for global generic parameters here. - auto type = varDecl->getType()->Substitute(globalGenericSubst).as<Type>(); + auto type = GetType(varDeclRef)->Substitute(globalGenericSubst).as<Type>(); // We use a single operation to both check whether the // variable represents a shader parameter, and to compute // the layout for that parameter's type. auto typeLayout = getTypeLayoutForGlobalShaderParameter( context, - varDecl, + varDeclRef.getDecl(), type); // If we did not find appropriate layout rules, then it @@ -1207,45 +704,43 @@ static void collectGlobalScopeParameter( // Now create a variable layout that we can use RefPtr<VarLayout> varLayout = new VarLayout(); varLayout->typeLayout = typeLayout; - varLayout->varDecl = DeclRef<Decl>(varDecl.Ptr(), nullptr).as<VarDeclBase>(); + varLayout->varDecl = varDeclRef; - // This declaration may represent the same logical parameter - // as a declaration that came from a different translation unit. - // If that is the case, we want to re-use the same `VarLayout` - // across both parameters. + // The logic in `check.cpp` that created the `GlobalShaderParamInfo` + // will have identified any cases where there might be multiple + // global variables that logically represent the same shader parameter. // - // TODO: This logic currently detects *any* global-scope parameters - // with matching names, but it should eventually be narrowly - // scoped so that it only applies to parameters from unnamed modules. + // We will track the same basic information during layout using + // the `ParameterInfo` type. // - // First we look for an existing entry matching the name - // of this parameter: - auto parameterName = getReflectionName(varDecl); - ParameterInfo* parameterInfo = nullptr; - if( context->mapNameToParameterInfo.TryGetValue(parameterName, parameterInfo) ) - { - // If the parameters have the same name, but don't "match" according to some reasonable rules, - // then we need to bail out. - if( !doesParameterMatch(context, varLayout, parameterInfo) ) - { - parameterInfo = nullptr; - } - } + // TODO: `ParameterInfo` should probably become `LayoutParamInfo`. + // + ParameterInfo* parameterInfo = new ParameterInfo(); + context->shared->parameters.Add(parameterInfo); - // If we didn't find a matching parameter, then we need to create one here - if( !parameterInfo ) - { - parameterInfo = new ParameterInfo(); - context->shared->parameters.Add(parameterInfo); - context->mapNameToParameterInfo.AddIfNotExists(parameterName, parameterInfo); - } - else + // Add the first variable declaration to the list of declarations for the parameter + parameterInfo->varLayouts.Add(varLayout); + + // Add any additional variables to the list of declarations + for( auto additionalVarDeclRef : shaderParamInfo.additionalParamDeclRefs ) { - varLayout->flags |= VarLayoutFlag::IsRedeclaration; - } + // TODO: We should either eliminate the design choice where different + // declarations of the "same" shade parameter get merged across + // translation units (it is effectively just a compatiblity feature), + // or we should clean things up earlier in the chain so that we can + // re-use a single `VarLayout` across all of the different declarations. + // + // TODO: It would also make sense in these cases to ensure that + // such global shader parameters get the same mangled name across + // all translation units, so that they can automatically be collapsed + // during linking. - // Add this variable declaration to the list of declarations for the parameter - parameterInfo->varLayouts.Add(varLayout); + RefPtr<VarLayout> additionalVarLayout = new VarLayout(); + additionalVarLayout->typeLayout = typeLayout; + additionalVarLayout->varDecl = additionalVarDeclRef; + + parameterInfo->varLayouts.Add(additionalVarLayout); + } } static RefPtr<UsedRangeSet> findUsedRangeSetForSpace( @@ -1783,41 +1278,6 @@ static void completeBindingsForParameter( applyBindingInfoToParameter(varLayout, bindingInfos); } - - -static void collectGlobalScopeParameters( - ParameterBindingContext* context, - ModuleDecl* program, - SubstitutionSet globalGenericSubst) -{ - // First enumerate parameters at global scope - // We collect two things here: - // 1. A shader parameter, which is always a variable - // 2. A global entry-point generic parameter type (`type_param`), - // which is a GlobalGenericParamDecl - // We collect global generic type parameters in the first pass, - // So we can fill in the correct index into ordinary type layouts - // for generic types in the second pass. - for (auto decl : program->Members) - { - if (auto genParamDecl = as<GlobalGenericParamDecl>(decl)) - collectGlobalGenericParameter(context, genParamDecl); - } - for (auto decl : program->Members) - { - if (auto varDecl = as<VarDeclBase>(decl)) - collectGlobalScopeParameter(context, varDecl, globalGenericSubst); - } - - // Next, we need to enumerate the parameters of - // each entry point (which requires knowing what the - // entry points *are*) - - // TODO(tfoley): Entry point functions should be identified - // by looking for a generated modifier that is attached - // to global-scope function declarations. -} - struct SimpleSemanticInfo { String name; @@ -2274,7 +1734,7 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( static RefPtr<TypeLayout> computeEntryPointParameterTypeLayout( ParameterBindingContext* context, SubstitutionSet typeSubst, - DeclRef<ParamDecl> paramDeclRef, + DeclRef<VarDeclBase> paramDeclRef, RefPtr<VarLayout> paramVarLayout, EntryPointParameterState& state) { @@ -2537,8 +1997,10 @@ static void collectEntryPointParameters( scopeBuilder.beginLayout(context); auto paramsStructLayout = scopeBuilder.m_structLayout; - for( auto paramDeclRef : getMembersOfType<ParamDecl>(entryPointFuncDeclRef) ) + for( auto& shaderParamInfo : entryPoint->getShaderParams() ) { + auto paramDeclRef = shaderParamInfo.paramDeclRef; + // Any error messages we emit during the process should // refer to the location of this parameter. // @@ -2656,17 +2118,33 @@ static void collectParameters( // logical namespace/"linkage" so that two parameters // with the same name should represent the same // parameter, and get the same binding(s) + ParameterBindingContext contextData = *inContext; auto context = &contextData; + context->stage = Stage::Unknown; auto globalGenericSubst = program->getGlobalGenericSubstitution(); + // We will start by looking for any global generic type parameters. + for(RefPtr<Module> module : program->getModuleDependencies()) { - context->stage = Stage::Unknown; + for( auto genParamDecl : module->getModuleDecl()->getMembersOfType<GlobalGenericParamDecl>() ) + { + collectGlobalGenericParameter(context, genParamDecl); + } + } + + // Once we have enumerated global generic type parameters, we can + // begin enumerating shader parameters, starting at the global scope. + // + // Because we have already enumerated the global generic type parameters, + // we will be able to look up the index of a global generic type parameter + // when we see it referenced in the type of one of the shader parameters. - // First look at global-scope parameters - collectGlobalScopeParameters(context, module->getModuleDecl(), globalGenericSubst); + for(auto& globalParamInfo : program->getShaderParams() ) + { + collectGlobalScopeParameter(context, globalParamInfo, globalGenericSubst); } // Next consider parameters for entry points diff --git a/source/slang/syntax.h b/source/slang/syntax.h index 5198a44b2..cb45c0363 100644 --- a/source/slang/syntax.h +++ b/source/slang/syntax.h @@ -1221,6 +1221,20 @@ namespace Slang return rs; } + /// The the user-level name for a variable that might be a shader parameter. + /// + /// In most cases this is just the name of the variable declaration itself, + /// but in the specific case of a `cbuffer`, the name that the user thinks + /// of is really metadata. For example: + /// + /// cbuffer C { int x; } + /// + /// In this example, error messages relating to the constant buffer should + /// really use the name `C`, but that isn't the name of the declaration + /// (it is in practice anonymous, and `C` can be used for a different + /// declaration in the same file). + /// + Name* getReflectionName(VarDeclBase* varDecl); inline RefPtr<Type> GetType(DeclRef<VarDeclBase> const& declRef) { diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h index 18a51c155..bb32d769e 100644 --- a/source/slang/type-layout.h +++ b/source/slang/type-layout.h @@ -376,7 +376,6 @@ public: typedef unsigned int VarLayoutFlags; enum VarLayoutFlag : VarLayoutFlags { - IsRedeclaration = 1 << 0, ///< This is a redeclaration of some shader parameter HasSemantic = 1 << 1 }; |
