Browse Source

Instantiate argument default parameters (#5443)

This addresses a latent bug in Clang that was fixed _way_ after DXC was
forked. We encounter it in DXC because of the way that we inject AST
bits through the external sema source, but it likely wasn't exposable
through C++.

This change is mostly a back porting of the following upstream LLVM
commits:

llvm-project/73c6a2448f24
llvm-project/f721e0582b15
llvm-project/c601377b2376
llvm-project/4409a83c2935

The end result of this change is that we still lazily instantiate
default arguments, but we do force them to be instantiated during sema
when they are used.

Fixes #5145
Chris B 2 years ago
parent
commit
573b652210

+ 16 - 0
tools/clang/include/clang/AST/DeclBase.h

@@ -721,6 +721,22 @@ public:
     return getParentFunctionOrMethod() == nullptr;
   }
 
+  /// HLSL Change Begin - back port from llvm-project/73c6a2448f24 &
+  /// f721e0582b15. Determine whether a substitution into this declaration would
+  /// occur as part of a substitution into a dependent local scope. Such a
+  /// substitution transitively substitutes into all constructs nested within
+  /// this declaration.
+  ///
+  /// This recognizes non-defining declarations as well as members of local
+  /// classes and lambdas:
+  /// \code
+  ///     template<typename T> void foo() { void bar(); }
+  ///     template<typename T> void foo2() { class ABC { void bar(); }; }
+  ///     template<typename T> inline int x = [](){ return 0; }();
+  /// \endcode
+  bool isInLocalScopeForInstantiation() const;
+  /// HLSL Change End - back port from llvm-project/73c6a2448f24 & f721e0582b15.
+
   /// \brief If this decl is defined inside a function/method/block it returns
   /// the corresponding DeclContext, otherwise it returns null.
   const DeclContext *getParentFunctionOrMethod() const;

+ 13 - 0
tools/clang/include/clang/Sema/Sema.h

@@ -2736,6 +2736,13 @@ private:
                              const ObjCObjectPointerType *OPT,
                              bool ErrorRecovery);
 
+  /// HLSL Change Begin - back ported from llvm-project/c601377b2376.
+  bool addInstantiatedParametersToScope(
+      FunctionDecl *Function, const FunctionDecl *PatternDecl,
+      LocalInstantiationScope &Scope,
+      const MultiLevelTemplateArgumentList &TemplateArgs);
+  /// HLSL Change End - back ported from llvm-project/c601377b2376.
+
 public:
   const TypoExprState &getTypoExprState(TypoExpr *TE) const;
 
@@ -6973,6 +6980,12 @@ public:
                       const MultiLevelTemplateArgumentList &TemplateArgs,
                       SmallVectorImpl<QualType> &ParamTypes,
                       SmallVectorImpl<ParmVarDecl *> *OutParams = nullptr);
+  /// HLSL Change Begin - back ported from llvm-project/4409a83c2935.
+  bool SubstDefaultArgument(SourceLocation Loc, ParmVarDecl *Param,
+                            const MultiLevelTemplateArgumentList &TemplateArgs,
+                            bool ForCallExpr = false);
+  /// HLSL Change End - back ported from llvm-project/4409a83c2935.
+
   ExprResult SubstExpr(Expr *E,
                        const MultiLevelTemplateArgumentList &TemplateArgs);
 

+ 19 - 0
tools/clang/lib/AST/DeclBase.cpp

@@ -254,6 +254,25 @@ void Decl::setDeclContextsImpl(DeclContext *SemaDC, DeclContext *LexicalDC,
   }
 }
 
+/// HLSL Change Begin - back port from llvm-project/73c6a2448f24 & f721e0582b15.
+bool Decl::isInLocalScopeForInstantiation() const {
+  const DeclContext *LDC = getLexicalDeclContext();
+  if (!LDC->isDependentContext())
+    return false;
+  while (true) {
+    if (LDC->isFunctionOrMethod())
+      return true;
+    if (!isa<TagDecl>(LDC))
+      return false;
+    if (const auto *CRD = dyn_cast<CXXRecordDecl>(LDC))
+      if (CRD->isLambda())
+        return true;
+    LDC = LDC->getLexicalParent();
+  }
+  return false;
+}
+/// HLSL Change End - back port from llvm-project/73c6a2448f24 & f721e0582b15.
+
 bool Decl::isInAnonymousNamespace() const {
   const DeclContext *DC = getDeclContext();
   do {

+ 4 - 39
tools/clang/lib/Sema/SemaExpr.cpp

@@ -4380,50 +4380,15 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
   }
   
   if (Param->hasUninstantiatedDefaultArg()) {
-    Expr *UninstExpr = Param->getUninstantiatedDefaultArg();
-
-    EnterExpressionEvaluationContext EvalContext(*this, PotentiallyEvaluated,
-                                                 Param);
-
+    /// HLSL Change Begin - back ported from llvm-project/4409a83c2935.
     // Instantiate the expression.
     MultiLevelTemplateArgumentList MutiLevelArgList
       = getTemplateInstantiationArgs(FD, nullptr, /*RelativeToPrimary=*/true);
 
-    InstantiatingTemplate Inst(*this, CallLoc, Param,
-                               MutiLevelArgList.getInnermost());
-    if (Inst.isInvalid())
-      return ExprError();
-
-    ExprResult Result;
-    {
-      // C++ [dcl.fct.default]p5:
-      //   The names in the [default argument] expression are bound, and
-      //   the semantic constraints are checked, at the point where the
-      //   default argument expression appears.
-      ContextRAII SavedContext(*this, FD);
-      LocalInstantiationScope Local(*this);
-      Result = SubstExpr(UninstExpr, MutiLevelArgList);
-    }
-    if (Result.isInvalid())
+    if (SubstDefaultArgument(CallLoc, Param, MutiLevelArgList,
+                             /*ForCallExpr*/ true))
       return ExprError();
-
-    // Check the expression as an initializer for the parameter.
-    InitializedEntity Entity
-      = InitializedEntity::InitializeParameter(Context, Param);
-    InitializationKind Kind
-      = InitializationKind::CreateCopy(Param->getLocation(),
-             /*FIXME:EqualLoc*/UninstExpr->getLocStart());
-    Expr *ResultE = Result.getAs<Expr>();
-
-    InitializationSequence InitSeq(*this, Entity, Kind, ResultE);
-    Result = InitSeq.Perform(*this, Entity, Kind, ResultE);
-    if (Result.isInvalid())
-      return ExprError();
-
-    Expr *Arg = Result.getAs<Expr>();
-    CheckCompletedExpr(Arg, Param->getOuterLocStart());
-    // Build the default argument expression.
-    return CXXDefaultArgExpr::Create(Context, CallLoc, Param, Arg);
+    /// HLSL Change End - back ported from llvm-project/4409a83c2935.
   }
 
   // If the default expression creates temporaries, we need to

+ 80 - 0
tools/clang/lib/Sema/SemaTemplateInstantiate.cpp

@@ -1742,6 +1742,86 @@ bool Sema::SubstParmTypes(SourceLocation Loc,
                                                   OutParams);
 }
 
+/// HLSL Change Begin - back ported from llvm-project/4409a83c2935.
+/// Substitute the given template arguments into the default argument.
+bool Sema::SubstDefaultArgument(
+    SourceLocation Loc,
+    ParmVarDecl *Param,
+    const MultiLevelTemplateArgumentList &TemplateArgs,
+    bool ForCallExpr) {
+  FunctionDecl *FD = cast<FunctionDecl>(Param->getDeclContext());
+  Expr *PatternExpr = Param->getUninstantiatedDefaultArg();
+
+  EnterExpressionEvaluationContext EvalContext(
+      *this, ExpressionEvaluationContext::PotentiallyEvaluated, Param);
+
+  InstantiatingTemplate Inst(*this, Loc, Param, TemplateArgs.getInnermost());
+  if (Inst.isInvalid())
+    return true;
+
+  ExprResult Result;
+  {
+    // C++ [dcl.fct.default]p5:
+    //   The names in the [default argument] expression are bound, and
+    //   the semantic constraints are checked, at the point where the
+    //   default argument expression appears.
+    ContextRAII SavedContext(*this, FD);
+    std::unique_ptr<LocalInstantiationScope> LIS;
+
+    if (ForCallExpr) {
+      // When instantiating a default argument due to use in a call expression,
+      // an instantiation scope that includes the parameters of the callee is
+      // required to satisfy references from the default argument. For example:
+      //   template<typename T> void f(T a, int = decltype(a)());
+      //   void g() { f(0); }
+      LIS = std::make_unique<LocalInstantiationScope>(*this);
+      FunctionDecl *PatternFD = FD->getTemplateInstantiationPattern();
+      if (addInstantiatedParametersToScope(FD, PatternFD, *LIS, TemplateArgs))
+        return true;
+    }
+
+    Result = SubstInitializer(PatternExpr, TemplateArgs,
+                              /*DirectInit*/false);
+  }
+  if (Result.isInvalid())
+    return true;
+
+  if (ForCallExpr) {
+    // Check the expression as an initializer for the parameter.
+    if (RequireCompleteType(Param->getLocation(), Param->getType(),
+                          diag::err_typecheck_decl_incomplete_type))
+       return true;
+    InitializedEntity Entity
+      = InitializedEntity::InitializeParameter(Context, Param);
+    InitializationKind Kind = InitializationKind::CreateCopy(
+        Param->getLocation(),
+        /*FIXME:EqualLoc*/ PatternExpr->getLocStart());
+    Expr *ResultE = Result.getAs<Expr>();
+
+    InitializationSequence InitSeq(*this, Entity, Kind, ResultE);
+    Result = InitSeq.Perform(*this, Entity, Kind, ResultE);
+    if (Result.isInvalid())
+      return true;
+
+    Result =
+        ActOnFinishFullExpr(Result.getAs<Expr>(), Param->getOuterLocStart(),
+                            /*DiscardedValue*/ false);
+  } else {
+    // FIXME: Obtain the source location for the '=' token.
+    SourceLocation EqualLoc = PatternExpr->getLocStart();
+    Result = SetParamDefaultArgument(Param, Result.getAs<Expr>(), EqualLoc);
+  }
+  if (Result.isInvalid())
+      return true;
+
+  // Remember the instantiated default argument.
+  Param->setDefaultArg(Result.getAs<Expr>());
+
+  return false;
+}
+
+/// HLSL Change End - back ported from llvm-project/4409a83c2935.
+
 /// \brief Perform substitution on the base class specifiers of the
 /// given class template specialization.
 ///

+ 68 - 10
tools/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

@@ -1565,6 +1565,33 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D,
       Previous.clear();
   }
 
+  // HLSL Change Begin - back ported from llvm-project/4409a83c2935.
+  // Per [temp.inst], default arguments in function declarations at local scope
+  // are instantiated along with the enclosing declaration. For example:
+  //
+  //   template<typename T>
+  //   void ft() {
+  //     void f(int = []{ return T::value; }());
+  //   }
+  //   template void ft<int>(); // error: type 'int' cannot be used prior
+  //                                      to '::' because it has no members
+  //
+  // The error is issued during instantiation of ft<int>() because substitution
+  // into the default argument fails; the default argument is instantiated even
+  // though it is never used.
+  if (Function->isLocalExternDecl()) {
+    for (ParmVarDecl *PVD : Function->parameters()) {
+      if (!PVD->hasDefaultArg())
+        continue;
+      if (SemaRef.SubstDefaultArgument(D->getInnerLocStart(), PVD,
+                                       TemplateArgs)) {
+        Function->setInvalidDecl();
+        return nullptr;
+      }
+    }
+  }
+  // HLSL Change End - back ported from llvm-project/4409a83c2935.
+
   SemaRef.CheckFunctionDeclaration(/*Scope*/ nullptr, Function, Previous,
                                    isExplicitSpecialization);
 
@@ -1862,6 +1889,33 @@ TemplateDeclInstantiator::VisitCXXMethodDecl(CXXMethodDecl *D,
       Previous.clear();
   }
 
+  // HLSL Change Begin - back ported from llvm-project/4409a83c2935.
+  // Per [temp.inst], default arguments in member functions of local classes
+  // are instantiated along with the member function declaration. For example:
+  //
+  //   template<typename T>
+  //   void ft() {
+  //     struct lc {
+  //       int operator()(int p = []{ return T::value; }());
+  //     };
+  //   }
+  //   template void ft<int>(); // error: type 'int' cannot be used prior
+  //                                      to '::'because it has no members
+  //
+  // The error is issued during instantiation of ft<int>()::lc::operator()
+  // because substitution into the default argument fails; the default argument
+  // is instantiated even though it is never used.
+  if (D->isInLocalScopeForInstantiation()) {
+    for (unsigned P = 0; P < Params.size(); ++P) {
+      if (!Params[P]->hasDefaultArg())
+        continue;
+      if (SemaRef.SubstDefaultArgument(StartLoc, Params[P], TemplateArgs)) {
+        return nullptr;
+      }
+    }
+  }
+  // HLSL Change End - back ported from llvm-project/4409a83c2935.
+
   if (!IsClassScopeSpecialization)
     SemaRef.CheckFunctionDeclaration(nullptr, Method, Previous, false);
 
@@ -3127,10 +3181,11 @@ TemplateDeclInstantiator::SubstFunctionType(FunctionDecl *D,
 /// Introduce the instantiated function parameters into the local
 /// instantiation scope, and set the parameter names to those used
 /// in the template.
-static bool addInstantiatedParametersToScope(Sema &S, FunctionDecl *Function,
-                                             const FunctionDecl *PatternDecl,
-                                             LocalInstantiationScope &Scope,
-                           const MultiLevelTemplateArgumentList &TemplateArgs) {
+/// HLSL Change Begin - back ported from llvm-project/601377b23767.
+bool Sema::addInstantiatedParametersToScope(
+    FunctionDecl *Function, const FunctionDecl *PatternDecl,
+    LocalInstantiationScope &Scope,
+    const MultiLevelTemplateArgumentList &TemplateArgs) {
   unsigned FParamIdx = 0;
   for (unsigned I = 0, N = PatternDecl->getNumParams(); I != N; ++I) {
     const ParmVarDecl *PatternParam = PatternDecl->getParamDecl(I);
@@ -3146,7 +3201,7 @@ static bool addInstantiatedParametersToScope(Sema &S, FunctionDecl *Function,
       // it's instantiation-dependent.
       // FIXME: Updating the type to work around this is at best fragile.
       if (!PatternDecl->getType()->isDependentType()) {
-        QualType T = S.SubstType(PatternParam->getType(), TemplateArgs,
+        QualType T = SubstType(PatternParam->getType(), TemplateArgs,
                                  FunctionParam->getLocation(),
                                  FunctionParam->getDeclName());
         if (T.isNull())
@@ -3162,7 +3217,7 @@ static bool addInstantiatedParametersToScope(Sema &S, FunctionDecl *Function,
     // Expand the parameter pack.
     Scope.MakeInstantiatedLocalArgPack(PatternParam);
     Optional<unsigned> NumArgumentsInExpansion
-      = S.getNumArgumentsInExpansion(PatternParam->getType(), TemplateArgs);
+      = getNumArgumentsInExpansion(PatternParam->getType(), TemplateArgs);
     assert(NumArgumentsInExpansion &&
            "should only be called when all template arguments are known");
     QualType PatternType =
@@ -3171,8 +3226,8 @@ static bool addInstantiatedParametersToScope(Sema &S, FunctionDecl *Function,
       ParmVarDecl *FunctionParam = Function->getParamDecl(FParamIdx);
       FunctionParam->setDeclName(PatternParam->getDeclName());
       if (!PatternDecl->getType()->isDependentType()) {
-        Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(S, Arg);
-        QualType T = S.SubstType(PatternType, TemplateArgs,
+        Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(*this, Arg);
+        QualType T = SubstType(PatternType, TemplateArgs,
                                  FunctionParam->getLocation(),
                                  FunctionParam->getDeclName());
         if (T.isNull())
@@ -3187,6 +3242,7 @@ static bool addInstantiatedParametersToScope(Sema &S, FunctionDecl *Function,
 
   return false;
 }
+/// HLSL Change End - back ported from llvm-project/601377b23767.
 
 void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation,
                                     FunctionDecl *Decl) {
@@ -3212,7 +3268,8 @@ void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation,
     getTemplateInstantiationArgs(Decl, nullptr, /*RelativeToPrimary*/true);
 
   FunctionDecl *Template = Proto->getExceptionSpecTemplate();
-  if (addInstantiatedParametersToScope(*this, Decl, Template, Scope,
+  // HLSL Change - back ported from llvm-project/601377b23767.
+  if (addInstantiatedParametersToScope(Decl, Template, Scope,
                                        TemplateArgs)) {
     UpdateExceptionSpec(Decl, EST_None);
     return;
@@ -3494,7 +3551,8 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
     // PushDeclContext because we don't have a scope.
     Sema::ContextRAII savedContext(*this, Function);
 
-    if (addInstantiatedParametersToScope(*this, Function, PatternDecl, Scope,
+    // HLSL Change - back ported from llvm-project/601377b23767.
+    if (addInstantiatedParametersToScope(Function, PatternDecl, Scope,
                                          TemplateArgs))
       return;
 

+ 1 - 2
tools/clang/test/CodeGenSPIRV/fn.param.default.hlsl

@@ -12,8 +12,7 @@ float4 main(uint vertex_id : SV_VertexID) : SV_Position
   // CHECK: OpStore %param_var_b %float_2
   // CHECK: [[first:%\d+]] = OpFunctionCall %float %test %param_var_a %param_var_b
   // CHECK: OpStore %param_var_a_0 %float_4
-  // CHECK: [[default:%\d+]] = OpConvertSToF %float %int_0
-  // CHECK: OpStore %param_var_b_0 [[default]]
+  // CHECK: OpStore %param_var_b_0 %float_0
   // CHECK: [[second:%\d+]] = OpFunctionCall %float %test %param_var_a_0 %param_var_b_0
   // CHECK: {{%\d+}} = OpCompositeConstruct %v4float [[first]] [[second]] %float_0 %float_0
   return float4(test<float>(1,2), test<float>(4), 0, 0);

+ 74 - 0
tools/clang/test/HLSLFileCheck/hlsl/template/InstantiateDefaultArg.hlsl

@@ -0,0 +1,74 @@
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -ast-dump | FileCheck %s -check-prefix=AST
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -fcgl | FileCheck %s
+
+template<typename T>
+T test(const T a, const T b = 0)
+{
+  return a + b;
+}
+
+// This test verifies a few different modes if instantiation for default
+// arguments.
+
+// The first chunk of AST being verified is the FunctionTemplateDecl. In the
+// template we have boh parameters defined and the second has a template pattern
+// for the `b` parameter.
+
+// AST: FunctionTemplateDecl {{.*}} test
+// AST-NEXT: TemplateTypeParmDecl {{.*}} referenced typename T
+// AST-NEXT: FunctionDecl {{.*}} test 'T (const T, const T)'
+// AST-NEXT: ParmVarDecl {{.*}} referenced a 'const T'
+// AST-NEXT: ParmVarDecl {{.*}} referenced b 'const T' cinit
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT: CompoundStmt
+// AST-NEXT: ReturnStmt
+// AST-NEXT: BinaryOperator {{.*}} 'const T' '+'
+// AST-NEXT: DeclRefExpr {{.*}} 'const T' lvalue ParmVar {{.*}} 'a' 'const T'
+// AST-NEXT: DeclRefExpr {{.*}} 'const T' lvalue ParmVar {{.*}} 'b' 'const T'
+
+// The second AST block is the test<float> instantiation. It has a full
+// instantiation including instantiation of the default template parameter. This
+// instantiation is called both with and without the default argument.
+
+// AST: FunctionDecl {{.*}} used test 'float (const float, const float)'
+// AST-NEXT: TemplateArgument type 'float'
+// AST-NEXT: ParmVarDecl {{.*}} used a 'const float':'const float'
+// AST-NEXT: ParmVarDecl {{.*}} used b 'const float':'const float' cinit
+// AST-NEXT: ImplicitCastExpr {{.*}} 'float':'float' <IntegralToFloating>
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT: CompoundStmt
+
+// The third AST block is the test<int> instantiation. This instantiation is
+// is also a full instantiation including the initializer for the `b` parameter.
+// This function is only called with the default argument used.
+
+// AST: FunctionDecl {{.*}} used test 'int (const int, const int)'
+// AST-NEXT: TemplateArgument type 'int'
+// AST-NEXT: ParmVarDecl {{.*}} used a 'const int':'const int'
+// AST-NEXT: ParmVarDecl {{.*}} used b 'const int':'const int' cinit
+// AST-NEXT: ImplicitCastExpr {{.*}} 'int':'int' <IntegralCast>
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT: CompoundStmt
+
+// The final AST block is the test<double> instantation. This instantiation does
+// not include instantiation of the default parameter. The default parameter is
+// only instantiated when used, and this instantation is only called providing
+// both arguments.
+
+// AST: FunctionDecl {{.*}} used test 'double (const double, const double)'
+// AST-NEXT: TemplateArgument type 'double'
+// AST-NEXT: ParmVarDecl {{.*}}  used a 'const double':'const double'
+// AST-NEXT: ParmVarDecl {{.*}} used b 'const double':'const double'
+// AST-NEXT: CompoundStmt
+
+
+float4 main(uint vertex_id : SV_VertexID) : SV_Position
+{
+  return float4(test<float>(1, 2), test<float>(4), (float)test<int>(5),
+                (float)test<double>(6, 7));
+}
+
+// CHECK: call float @"\01??$test{{.*}}"(float 1.000000e+00, float 2.000000e+00)
+// CHECK: call float @"\01??$test{{.*}}"(float 4.000000e+00, float 0.000000e+00)
+// CHECK: call i32 @"\01??$test{{.*}}"(i32 5, i32 0)
+// CHECK: call double @"\01??$test{{.*}}"(double 6.000000e+00, double 7.000000e+00)

+ 86 - 0
tools/clang/test/HLSLFileCheck/hlsl/template/InstantiateDefaultArgMember.hlsl

@@ -0,0 +1,86 @@
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -ast-dump | FileCheck %s -check-prefix=AST
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -fcgl | FileCheck %s
+
+template<typename T>
+struct Pupper {
+  T test(const T a, const T b = 0)
+  {
+    return a + b;
+  }
+};
+
+// This test verifies a few different modes if instantiation for default
+// arguments.
+
+// The first chunk of AST being verified is the ClassTemplateDecl for Pupper. In
+// the template we have boh parameters defined and the second has a template
+// pattern for the `b` parameter.
+
+// AST: ClassTemplateDecl {{.*}} Pupper
+// AST-NEXT: TemplateTypeParmDecl {{.*}} referenced typename T
+// AST-NEXT: CXXRecordDecl {{.*}} struct Pupper definition
+// AST-NEXT: CXXRecordDecl {{.*}} implicit struct Pupper
+// AST-NEXT: CXXMethodDecl {{.*}} test 'T (const T, const T)'
+// AST-NEXT: ParmVarDecl {{.*}} referenced a 'const T'
+// AST-NEXT: ParmVarDecl {{.*}} referenced b 'const T' cinit
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT: CompoundStmt
+// AST-NEXT: ReturnStmt
+// AST-NEXT: BinaryOperator {{.*}} 'const T' '+'
+// AST-NEXT: DeclRefExpr {{.*}} 'const T' lvalue ParmVar {{.*}} 'a' 'const T'
+// AST-NEXT: DeclRefExpr {{.*}} 'const T' lvalue ParmVar {{.*}} 'b' 'const T'
+
+// The second AST block is the Pupper<float> instantiation. It has a full
+// instantiation including instantiation of the default template parameter. This
+// instantiation is called both with and without the default argument.
+
+// AST: ClassTemplateSpecializationDecl {{.*}} struct Pupper definition
+// AST-NEXT: TemplateArgument type 'float'
+// AST-NEXT: CXXRecordDecl {{.*}} implicit struct Pupper
+// AST-NEXT: `-CXXMethodDecl {{.*}} used test 'float (const float, const float)'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used a 'const float':'const float'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used b 'const float':'const float' cinit
+// AST-NEXT:   | `-ImplicitCastExpr {{.*}} 'float':'float' <IntegralToFloating>
+// AST-NEXT:   |   `-IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT:   `-CompoundStmt
+
+// The third AST block is the Pupper<int> instantiation. This instantiation is
+// is also a full instantiation including the initializer for the `b` parameter.
+// This function is only called with the default argument used.
+
+// AST: ClassTemplateSpecializationDecl {{.*}} struct Pupper definition
+// AST-NEXT: |-TemplateArgument type 'int'
+// AST-NEXT: |-CXXRecordDecl {{.*}} implicit struct Pupper
+// AST-NEXT: `-CXXMethodDecl {{.*}} used test 'int (const int, const int)'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used a 'const int':'const int'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used b 'const int':'const int' cinit
+// AST-NEXT:   | `-ImplicitCastExpr {{.*}} 'int':'int' <IntegralCast>
+// AST-NEXT:   |   `-IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT:   `-CompoundStmt
+
+// The final AST block is the Pupper<double> instantation. This instantiation
+// does not include instantiation of the default parameter. The default
+// parameter is only instantiated when used, and this instantation is only
+// called providing both arguments.
+
+// AST: ClassTemplateSpecializationDecl {{.*}} struct Pupper definition
+// AST-NEXT: |-TemplateArgument type 'double'
+// AST-NEXT: |-CXXRecordDecl {{.*}} implicit struct Pupper
+// AST-NEXT: `-CXXMethodDecl {{.*}} used test 'double (const double, const double)'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used a 'const double':'const double'
+// AST-NEXT:   |-ParmVarDecl {{.*}} used b 'const double':'const double'
+// AST-NEXT:   `-CompoundStmt 
+
+float4 main(uint vertex_id : SV_VertexID) : SV_Position
+{
+  Pupper<float> PF;
+  Pupper<int> PI;
+  Pupper<double> PD;
+  return float4(PF.test(1, 2), PF.test(4), (float)PI.test(5),
+                (float)PD.test(6, 7));
+}
+
+// CHECK: call float @"\01?test@?$Pupper@{{.*}}"(%"struct.Pupper<float>"* {{.*}}, float 1.000000e+00, float 2.000000e+00)
+// CHECK: call float @"\01?test@?$Pupper@{{.*}}"(%"struct.Pupper<float>"* {{.*}}, float 4.000000e+00, float 0.000000e+00)
+// CHECK: call i32 @"\01?test@?$Pupper@{{.*}}"(%"struct.Pupper<int>"* {{.*}}, i32 5, i32 0)
+// CHECK: call double @"\01?test@?$Pupper@{{.*}}"(%"struct.Pupper<double>"* {{.*}}, double 6.000000e+00, double 7.000000e+00)

+ 36 - 0
tools/clang/test/HLSLFileCheck/hlsl/template/InstantiateDefaultArgResource.hlsl

@@ -0,0 +1,36 @@
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -ast-dump | FileCheck %s -check-prefix=AST
+// RUN: %dxc -T vs_6_6 -HV 2021 %s -fcgl | FileCheck %s
+
+// AST: FunctionTemplateDecl {{.*}} foo
+// AST-NEXT: TemplateTypeParmDecl {{.*}} referenced typename T
+// AST-NEXT: FunctionDecl {{.*}} foo 'void (Texture2D<vector<T, 4> >)'
+// AST-NEXT: ParmVarDecl {{.*}} tex 'Texture2D<vector<T, 4> >' cinit
+// AST-NEXT: CXXOperatorCallExpr {{.*}} 'const .Resource'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'const .Resource (*)(unsigned int) const' <FunctionToPointerDecay>
+// AST-NEXT: DeclRefExpr {{.*}} 'const .Resource (unsigned int) const' lvalue CXXMethod 0x{{[0-9a-fA-F]+}} 'operator[]' 'const .Resource (unsigned int) const'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'const .Resource' lvalue <NoOp>
+// AST-NEXT: DeclRefExpr {{.*}} '.Resource' lvalue Var 0x{{[0-9a-fA-F]+}} 'ResourceDescriptorHeap' '.Resource'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+// AST-NEXT: CompoundStmt
+
+// AST: FunctionDecl {{.*}} used foo 'void (Texture2D<vector<float, 4> >)'
+// AST-NEXT: TemplateArgument type 'float'
+// AST-NEXT: ParmVarDecl {{.*}}5 tex 'Texture2D<vector<float, 4> >':'Texture2D<vector<float, 4> >' cinit
+// AST-NEXT: ImplicitCastExpr {{.*}} 'Texture2D<vector<float, 4> >' <FlatConversion>
+// AST-NEXT: CXXOperatorCallExpr {{.*}} 'const .Resource'
+// AST-NEXT: ImplicitCastExpr {{.*}}'const .Resource (*)(unsigned int) const' <FunctionToPointerDecay>
+// AST-NEXT: DeclRefExpr {{.*}} 'const .Resource (unsigned int) const' lvalue CXXMethod 0x{{[0-9a-fA-F]+}} 'operator[]' 'const .Resource (unsigned int) const'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'const .Resource' lvalue <NoOp>
+// AST-NEXT: DeclRefExpr {{.*}} '.Resource' lvalue Var 0x{{[0-9a-fA-F]+}} 'ResourceDescriptorHeap' '.Resource'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'unsigned int' <IntegralCast>
+// AST-NEXT: IntegerLiteral {{.*}} 'literal int' 0
+
+template<typename T>
+void foo(Texture2D<vector<T, 4> > tex = ResourceDescriptorHeap[0]) {}
+
+void main() {
+  foo<float>();
+}
+
+// CHECK: call void @"\01??$foo{{.*}}"(%"class.Texture2D<vector<float, 4> >"* {{.*}})