https://github.com/inbelic updated 
https://github.com/llvm/llvm-project/pull/225452

>From 6c97763aacf05b86ff1bdb2767d5bbe60331d47e Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Fri, 18 Sep 2026 17:22:07 +0000
Subject: [PATCH 01/10] parse interpolation modifier keywords

---
 clang/include/clang/Basic/Attr.td             |  8 ++++++
 clang/include/clang/Basic/TokenKinds.def      |  6 ++++
 clang/lib/Parse/ParseDecl.cpp                 | 28 ++++++++++++++++++-
 clang/lib/Parse/ParseTentative.cpp            |  8 +++++-
 .../ParserHLSL/interpolation-keywords.hlsl    |  6 ++++
 .../BuiltIns/resource_load-errors.hlsl        |  4 +--
 6 files changed, 56 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/ParserHLSL/interpolation-keywords.hlsl

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index b9eb41654a81b..5e7dd712b0aae 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5321,6 +5321,14 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
   let Documentation = [HLSLGroupSharedAddressSpaceDocs];
 }
 
+// Parse interpolation modifiers without attaching them to declarations yet.
+def HLSLInterpolationModifier : IgnoredAttr {
+  let Spellings = [CustomKeyword<"nointerpolation">, CustomKeyword<"linear">,
+                   CustomKeyword<"centroid">, CustomKeyword<"noperspective">,
+                   CustomKeyword<"sample">, CustomKeyword<"center">];
+  let LangOpts = [HLSL];
+}
+
 def HLSLParamModifier : ParameterABIAttr {
   let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, 
CustomKeyword<"out">];
   let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
diff --git a/clang/include/clang/Basic/TokenKinds.def 
b/clang/include/clang/Basic/TokenKinds.def
index dc9c7d8109467..153caa568cd36 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -544,6 +544,12 @@ KEYWORD(__noinline__                , KEYCUDA)
 KEYWORD(cbuffer                     , KEYHLSL)
 KEYWORD(tbuffer                     , KEYHLSL)
 KEYWORD(groupshared                 , KEYHLSL)
+KEYWORD(nointerpolation             , KEYHLSL)
+KEYWORD(linear                      , KEYHLSL)
+KEYWORD(centroid                    , KEYHLSL)
+KEYWORD(noperspective               , KEYHLSL)
+KEYWORD(sample                      , KEYHLSL)
+KEYWORD(center                      , KEYHLSL)
 KEYWORD(in                          , KEYHLSL)
 KEYWORD(inout                       , KEYHLSL)
 KEYWORD(out                         , KEYHLSL)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 5976f5a7ccdea..6fb777a9416bc 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -1094,7 +1094,9 @@ void Parser::ParseOpenCLQualifiers(ParsedAttributes 
&Attrs) {
 
 bool Parser::isHLSLQualifier(const Token &Tok) const {
   return Tok.is(tok::kw_groupshared) || Tok.is(tok::kw_row_major) ||
-         Tok.is(tok::kw_column_major);
+         Tok.is(tok::kw_column_major) ||
+         Tok.isOneOf(tok::kw_nointerpolation, tok::kw_linear, tok::kw_centroid,
+                     tok::kw_noperspective, tok::kw_sample, tok::kw_center);
 }
 
 void Parser::ParseHLSLQualifiers(ParsedAttributes &Attrs) {
@@ -4657,6 +4659,12 @@ void Parser::ParseDeclarationSpecifiers(
       break;
     case tok::kw_row_major:
     case tok::kw_column_major:
+    case tok::kw_nointerpolation:
+    case tok::kw_linear:
+    case tok::kw_centroid:
+    case tok::kw_noperspective:
+    case tok::kw_sample:
+    case tok::kw_center:
     case tok::kw_groupshared:
     case tok::kw_in:
     case tok::kw_inout:
@@ -5771,6 +5779,12 @@ bool Parser::isTypeSpecifierQualifier(const Token &Tok) {
     return true;
 
   // HLSL type qualifiers
+  case tok::kw_nointerpolation:
+  case tok::kw_linear:
+  case tok::kw_centroid:
+  case tok::kw_noperspective:
+  case tok::kw_sample:
+  case tok::kw_center:
   case tok::kw_groupshared:
   case tok::kw_in:
   case tok::kw_inout:
@@ -6055,6 +6069,12 @@ bool Parser::isDeclarationSpecifier(
   case tok::kw_groupshared:
     return true;
 
+  case tok::kw_nointerpolation:
+  case tok::kw_linear:
+  case tok::kw_centroid:
+  case tok::kw_noperspective:
+  case tok::kw_sample:
+  case tok::kw_center:
   case tok::kw_row_major:
   case tok::kw_column_major:
     return getLangOpts().HLSL;
@@ -6301,6 +6321,12 @@ void Parser::ParseTypeQualifierListOpt(
     case tok::kw_in:
     case tok::kw_inout:
     case tok::kw_out:
+    case tok::kw_nointerpolation:
+    case tok::kw_linear:
+    case tok::kw_centroid:
+    case tok::kw_noperspective:
+    case tok::kw_sample:
+    case tok::kw_center:
       // NOTE: ParseHLSLQualifiers will consume the qualifier token.
       ParseHLSLQualifiers(DS.getAttributes());
       continue;
diff --git a/clang/lib/Parse/ParseTentative.cpp 
b/clang/lib/Parse/ParseTentative.cpp
index 6491baf0af92c..989801b0cfa84 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -1286,7 +1286,13 @@ 
Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
     // OpenCL pipe
   case tok::kw_pipe:
 
-    // HLSL address space qualifiers
+    // HLSL interpolation and address space qualifiers
+  case tok::kw_nointerpolation:
+  case tok::kw_linear:
+  case tok::kw_centroid:
+  case tok::kw_noperspective:
+  case tok::kw_sample:
+  case tok::kw_center:
   case tok::kw_groupshared:
   case tok::kw_in:
   case tok::kw_inout:
diff --git a/clang/test/ParserHLSL/interpolation-keywords.hlsl 
b/clang/test/ParserHLSL/interpolation-keywords.hlsl
new file mode 100644
index 0000000000000..462c021ce986d
--- /dev/null
+++ b/clang/test/ParserHLSL/interpolation-keywords.hlsl
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -x c++ -verify %s
+// RUN: %clang_cc1 -x c -verify %s
+// expected-no-diagnostics
+
+// Interpolation modifiers are keywords only in HLSL.
+int nointerpolation, linear, centroid, noperspective, sample, center;
diff --git a/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl 
b/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl
index b22dae4d1bae9..02fcad2801121 100644
--- a/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl
@@ -31,9 +31,9 @@ export void load(tex_t t, int3 loc, vector<int64_t,3> 
wide_loc, uint3 uloc,
   __builtin_hlsl_resource_load_level(t, loc, foff);
 }
 
-export void load_ms(tex_ms_t t, int2 loc, int sample, int64_t lsample,
+export void load_ms(tex_ms_t t, int2 loc, int sample_index, int64_t lsample,
                     float fsample, int2 off, uint2 uoff, float2 foff) {
-  __builtin_hlsl_resource_load_ms(t, loc, sample);
+  __builtin_hlsl_resource_load_ms(t, loc, sample_index);
   __builtin_hlsl_resource_load_ms(t, loc, 0, off);
 
   // expected-error@+1 {{passing 'int64_t' (aka 'long') to parameter of 
incompatible type 'int'}}

>From e90131695887981fcd703fb784d334afaae6e243 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Fri, 18 Sep 2026 17:22:25 +0000
Subject: [PATCH 02/10] attach and validate interpolation modifier attributes

---
 clang/include/clang/Basic/Attr.td             |  10 +-
 clang/include/clang/Basic/AttrDocs.td         |  18 ++
 .../clang/Basic/DiagnosticSemaKinds.td        |  12 ++
 clang/include/clang/Sema/SemaHLSL.h           |   6 +
 clang/lib/Sema/SemaDeclAttr.cpp               |   3 +
 clang/lib/Sema/SemaHLSL.cpp                   | 149 ++++++++++++--
 .../interpolation-modifier-warnings.hlsl      |  12 ++
 .../SemaHLSL/interpolation-modifiers-ast.hlsl |  61 ++++++
 .../SemaHLSL/interpolation-modifiers.hlsl     | 185 ++++++++++++++++++
 .../llvm/Frontend/HLSL/SemanticSignatures.h   |  31 +++
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp |  35 ++++
 llvm/unittests/Frontend/CMakeLists.txt        |   1 +
 .../Frontend/HLSLInterpolationTest.cpp        |  72 +++++++
 13 files changed, 582 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl
 create mode 100644 clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl
 create mode 100644 clang/test/SemaHLSL/interpolation-modifiers.hlsl
 create mode 100644 llvm/unittests/Frontend/HLSLInterpolationTest.cpp

diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 5e7dd712b0aae..5491491d60011 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -5321,12 +5321,18 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
   let Documentation = [HLSLGroupSharedAddressSpaceDocs];
 }
 
-// Parse interpolation modifiers without attaching them to declarations yet.
-def HLSLInterpolationModifier : IgnoredAttr {
+def HLSLInterpolationModifiersArgument : UnsignedArgument<"Modifiers"> {
+  let Fake = 1;
+}
+
+def HLSLInterpolationModifier : InheritableAttr {
   let Spellings = [CustomKeyword<"nointerpolation">, CustomKeyword<"linear">,
                    CustomKeyword<"centroid">, CustomKeyword<"noperspective">,
                    CustomKeyword<"sample">, CustomKeyword<"center">];
+  let Args = [HLSLInterpolationModifiersArgument];
+  let Subjects = SubjectList<[ParmVar, Field, Function], ErrorDiag>;
   let LangOpts = [HLSL];
+  let Documentation = [HLSLInterpolationModifierDocs];
 }
 
 def HLSLParamModifier : ParameterABIAttr {
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 6170688ae8cf9..5fe9d76ba967d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9384,6 +9384,24 @@ The full documentation is available here: 
<https://learn.microsoft.com/en-us/win
   }];
 }
 
+def HLSLInterpolationModifierDocs : Documentation {
+  let Category = DocCatVariable;
+  let Heading = "HLSL Interpolation Modifiers";
+  let Content = [{
+The HLSL keywords `nointerpolation`, `linear`, `centroid`,
+`noperspective`, `sample`, and `center` control interpolation of pixel
+shader inputs.
+
+When applied to an aggregate type, the modifier propagates recursively to every
+scalar or vector of the type. A modifier on an inner field overrides one
+inherited from an enclosing declaration.
+
+`nointerpolation` cannot be combined with another interpolation modifier and
+cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point
+input components only support `nointerpolation`.
+  }];
+}
+
 def HLSLParamQualifierDocs : Documentation {
   let Category = DocCatVariable;
   let Heading = "HLSL Parameter Modifiers";
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 36a18473f4d4c..c84e9a18cadd6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13706,6 +13706,18 @@ def err_hlsl_numthreads_invalid : Error<"total number 
of threads cannot exceed %
 def err_hlsl_missing_numthreads : Error<"missing numthreads attribute for %0 
shader entry">;
 def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not 
match the previous declaration">;
 def err_hlsl_duplicate_parameter_modifier : Error<"duplicate parameter 
modifier %0">;
+def err_hlsl_interpolation_conflict : Error<
+  "'nointerpolation' cannot be combined with other interpolation modifiers">;
+def err_hlsl_interpolation_position : Error<
+  "'nointerpolation' cannot be used on SV_Position">;
+def err_hlsl_interpolation_type : Error<
+  "interpolation modifiers other than 'nointerpolation' cannot be used with 
type %0">;
+def warn_hlsl_duplicate_interpolation : Warning<
+  "duplicate interpolation modifier %0">,
+  InGroup<DuplicateDeclSpecifier>;
+def warn_hlsl_interpolation_override : Warning<
+  "interpolation modifier '%0' overrides '%1'">,
+  InGroup<IgnoredAttributes>;
 def err_hlsl_missing_semantic_annotation : Error<
   "semantic annotations must be present for all parameters of an entry "
   "function or patch constant function">;
diff --git a/clang/include/clang/Sema/SemaHLSL.h 
b/clang/include/clang/Sema/SemaHLSL.h
index 6c0e5b52f7cb3..764635e40ce5a 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -191,6 +191,7 @@ class SemaHLSL : public SemaBase {
   void handleShaderAttr(Decl *D, const ParsedAttr &AL);
   void handleResourceBindingAttr(Decl *D, const ParsedAttr &AL);
   void handleParamModifierAttr(Decl *D, const ParsedAttr &AL);
+  void handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL);
   Attr *buildMatrixLayoutTypeAttr(QualType T, const ParsedAttr &AL);
   bool diagnoseMatrixLayoutInstantiation(attr::Kind K, QualType T,
                                          SourceLocation Loc);
@@ -308,6 +309,11 @@ class SemaHLSL : public SemaBase {
   };
 
 private:
+  bool
+  checkInterpolationModifiers(const DeclaratorDecl *D,
+                              const HLSLInterpolationModifierAttr *Inherited,
+                              const HLSLParsedSemanticAttr *Semantic);
+
   void collectResourceBindingsOnVarDecl(VarDecl *D);
   void collectResourceBindingsOnUserRecordDecl(const VarDecl *VD,
                                                const RecordType *RT);
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index eb4a8c2ab9ae0..da7472c1cba1b 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8272,6 +8272,9 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr 
&AL,
   case ParsedAttr::AT_HLSLResourceBinding:
     S.HLSL().handleResourceBindingAttr(D, AL);
     break;
+  case ParsedAttr::AT_HLSLInterpolationModifier:
+    S.HLSL().handleInterpolationModifierAttr(D, AL);
+    break;
   case ParsedAttr::AT_HLSLParamModifier:
     S.HLSL().handleParamModifierAttr(D, AL);
     break;
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 4a64e9ea8a1dd..17664ca7451df 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -47,6 +47,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/TargetParser/Triple.h"
+#include <algorithm>
 #include <cmath>
 #include <cstddef>
 #include <iterator>
@@ -54,6 +55,7 @@
 
 using namespace clang;
 using namespace clang::hlsl;
+using llvm::hlsl::InterpolationModifier;
 using llvm::hlsl::IOType;
 using llvm::hlsl::SemanticStageInfo;
 using SemanticKind = llvm::dxbc::PSV::SemanticKind;
@@ -62,6 +64,14 @@ using RegisterType = HLSLResourceBindingAttr::RegisterType;
 static CXXRecordDecl *createHostLayoutStruct(Sema &S,
                                              CXXRecordDecl *StructDecl);
 
+static QualType getScalarComponentType(QualType T) {
+  if (const auto *VT = T->getAs<VectorType>())
+    return VT->getElementType();
+  if (const auto *MT = T->getAs<MatrixType>())
+    return MT->getElementType();
+  return T;
+}
+
 static RegisterType getRegisterType(ResourceClass RC) {
   switch (RC) {
   case ResourceClass::SRV:
@@ -817,6 +827,126 @@ SemaHLSL::mergeParamModifierAttr(Decl *D, const 
AttributeCommonInfo &AL,
   return HLSLParamModifierAttr::Create(getASTContext(), AL);
 }
 
+static StringRef
+getInterpolationSamplingLocationName(InterpolationModifier Location) {
+  switch (Location) {
+  case InterpolationModifier::Sample:
+    return "sample";
+  case InterpolationModifier::Centroid:
+    return "centroid";
+  case InterpolationModifier::Center:
+    return "center";
+  default:
+    llvm_unreachable("expected an explicit interpolation sampling location");
+  }
+}
+
+void SemaHLSL::handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL) {
+  InterpolationModifier Modifier;
+  switch (static_cast<HLSLInterpolationModifierAttr::Spelling>(
+      AL.getSemanticSpelling())) {
+  case HLSLInterpolationModifierAttr::Keyword_nointerpolation:
+    Modifier = InterpolationModifier::NoInterpolation;
+    break;
+  case HLSLInterpolationModifierAttr::Keyword_linear:
+    Modifier = InterpolationModifier::Linear;
+    break;
+  case HLSLInterpolationModifierAttr::Keyword_centroid:
+    Modifier = InterpolationModifier::Centroid;
+    break;
+  case HLSLInterpolationModifierAttr::Keyword_noperspective:
+    Modifier = InterpolationModifier::NoPerspective;
+    break;
+  case HLSLInterpolationModifierAttr::Keyword_sample:
+    Modifier = InterpolationModifier::Sample;
+    break;
+  case HLSLInterpolationModifierAttr::Keyword_center:
+    Modifier = InterpolationModifier::Center;
+    break;
+  case HLSLInterpolationModifierAttr::SpellingNotCalculated:
+    llvm_unreachable("interpolation modifier spelling was not calculated");
+  }
+
+  InterpolationModifier Modifiers = Modifier;
+  if (auto *Previous = D->getAttr<HLSLInterpolationModifierAttr>()) {
+    auto Old = static_cast<InterpolationModifier>(Previous->getModifiers());
+    Modifiers |= Old;
+    if (any(Old & Modifier)) {
+      Diag(AL.getLoc(), diag::warn_hlsl_duplicate_interpolation) << AL;
+    } else if (llvm::hlsl::getInterpolationMode(Modifiers) ==
+                   llvm::dxbc::PSV::InterpolationMode::Invalid &&
+               llvm::hlsl::getInterpolationMode(Old) !=
+                   llvm::dxbc::PSV::InterpolationMode::Invalid) {
+      Diag(AL.getLoc(), diag::err_hlsl_interpolation_conflict);
+      Diag(Previous->getLocation(), diag::note_conflicting_attribute);
+      D->setInvalidDecl();
+    } else {
+      InterpolationModifier OldLocation =
+          llvm::hlsl::getInterpolationSamplingLocation(Old);
+      InterpolationModifier NewLocation =
+          llvm::hlsl::getInterpolationSamplingLocation(Modifier);
+      if (any(OldLocation) && any(NewLocation)) {
+        Diag(AL.getLoc(), diag::warn_hlsl_interpolation_override)
+            << getInterpolationSamplingLocationName(
+                   std::max(OldLocation, NewLocation))
+            << getInterpolationSamplingLocationName(
+                   std::min(OldLocation, NewLocation));
+      }
+    }
+    D->dropAttr<HLSLInterpolationModifierAttr>();
+  }
+  D->addAttr(HLSLInterpolationModifierAttr::Create(
+      getASTContext(), static_cast<unsigned>(Modifiers), AL));
+}
+
+bool SemaHLSL::checkInterpolationModifiers(
+    const DeclaratorDecl *D, const HLSLInterpolationModifierAttr *Inherited,
+    const HLSLParsedSemanticAttr *Semantic) {
+  if (D->isInvalidDecl())
+    return false;
+  const auto *A = D->getAttr<HLSLInterpolationModifierAttr>();
+  if (!A)
+    A = Inherited;
+  if (!Semantic)
+    Semantic = D->getAttr<HLSLParsedSemanticAttr>();
+
+  QualType T =
+      getASTContext().getBaseElementType(D->getType().getNonReferenceType());
+  if (T->isDependentType())
+    return true;
+  if (const auto *RT = T->getAs<RecordType>()) {
+    const RecordDecl *RD = RT->getDecl()->getDefinition();
+    if (!RD)
+      return true;
+    bool Valid = true;
+    for (const FieldDecl *Field : RD->fields())
+      Valid &= checkInterpolationModifiers(Field, A, Semantic);
+    return Valid;
+  }
+  if (!A)
+    return true;
+
+  auto Modifiers = static_cast<InterpolationModifier>(A->getModifiers());
+  if (Modifiers == InterpolationModifier::NoInterpolation) {
+    bool IsPosition =
+        Semantic && llvm::hlsl::getSemanticKind(Semantic->getSemanticName()) ==
+                        SemanticKind::Position;
+    if (!IsPosition)
+      return true;
+    Diag(A->getLocation(), diag::err_hlsl_interpolation_position);
+    Diag(Semantic->getLocation(), diag::note_conflicting_attribute);
+    return false;
+  }
+
+  T = getScalarComponentType(T);
+  if (T->isIntegerType() ||
+      (T->isRealFloatingType() && getASTContext().getTypeSize(T) > 32)) {
+    Diag(A->getLocation(), diag::err_hlsl_interpolation_type) << T;
+    return false;
+  }
+  return true;
+}
+
 void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
   auto &TargetInfo = getASTContext().getTargetInfo();
 
@@ -1067,6 +1197,12 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
     const auto *MA = Param->getAttr<HLSLParamModifierAttr>();
     SemanticContext &SC = MA && MA->isAnyOut() ? OutputSC : InputSC;
 
+    // Interpolation only applies to pixel shader inputs, including the input
+    // side of inout parameters. Other stages and output signatures ignore it.
+    if (ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn()) &&
+        !checkInterpolationModifiers(Param, nullptr, nullptr))
+      FD->setInvalidDecl();
+
     if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic, SC)) {
       Diag(Param->getLocation(), diag::note_previous_decl) << Param;
       FD->setInvalidDecl();
@@ -3346,11 +3482,7 @@ static bool CheckFloatRepresentation(Sema *S, 
SourceLocation Loc,
 static bool CheckFloatOrHalfRepresentation(Sema *S, SourceLocation Loc,
                                            int ArgOrdinal,
                                            clang::QualType PassedType) {
-  clang::QualType BaseType = PassedType;
-  if (const auto *VT = PassedType->getAs<clang::VectorType>())
-    BaseType = VT->getElementType();
-  else if (const auto *MT = PassedType->getAs<clang::MatrixType>())
-    BaseType = MT->getElementType();
+  QualType BaseType = getScalarComponentType(PassedType);
 
   if (!BaseType->isHalfType() && !BaseType->isFloat32Type())
     return S->Diag(Loc, diag::err_builtin_invalid_arg_type)
@@ -3362,12 +3494,7 @@ static bool CheckFloatOrHalfRepresentation(Sema *S, 
SourceLocation Loc,
 static bool CheckAnyDoubleRepresentation(Sema *S, SourceLocation Loc,
                                          int ArgOrdinal,
                                          clang::QualType PassedType) {
-  clang::QualType BaseType =
-      PassedType->isVectorType()
-          ? PassedType->castAs<clang::VectorType>()->getElementType()
-      : PassedType->isMatrixType()
-          ? PassedType->castAs<clang::MatrixType>()->getElementType()
-          : PassedType;
+  QualType BaseType = getScalarComponentType(PassedType);
   if (!BaseType->isDoubleType()) {
     // FIXME: adopt standard `err_builtin_invalid_arg_type` instead of using
     // this custom error.
diff --git a/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl 
b/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl
new file mode 100644
index 0000000000000..95791316f7469
--- /dev/null
+++ b/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -verify %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-Wno-duplicate-decl-specifier -verify=override %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-Wno-ignored-attributes -verify=duplicate %s
+
+// The existing diagnostic groups independently control duplicates and 
overrides.
+// expected-warning@+2 {{duplicate interpolation modifier 'linear'}}
+// duplicate-warning@+1 {{duplicate interpolation modifier 'linear'}}
+void duplicates(linear linear float x);
+
+// expected-warning@+2 {{interpolation modifier 'sample' overrides 'center'}}
+// override-warning@+1 {{interpolation modifier 'sample' overrides 'center'}}
+void locations(center sample float x);
diff --git a/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl 
b/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl
new file mode 100644
index 0000000000000..be892d68d6fef
--- /dev/null
+++ b/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-Wno-duplicate-decl-specifier -Wno-ignored-attributes -ast-dump %s | FileCheck 
%s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-Wno-duplicate-decl-specifier -Wno-ignored-attributes -emit-pch -o %t %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -include-pch %t 
-ast-dump-all -x hlsl /dev/null | FileCheck %s
+
+void modifiers(nointerpolation float a, linear float b, centroid float c,
+               noperspective float d, sample float e, center float f);
+// CHECK-LABEL: FunctionDecl {{.*}} modifiers
+// CHECK: ParmVarDecl {{.*}} a 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 1
+// CHECK: ParmVarDecl {{.*}} b 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2
+// CHECK: ParmVarDecl {{.*}} c 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 16
+// CHECK: ParmVarDecl {{.*}} d 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 4
+// CHECK: ParmVarDecl {{.*}} e 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32
+// CHECK: ParmVarDecl {{.*}} f 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 8
+
+void merged(linear sample noperspective float a,
+            noperspective sample linear float b,
+            center centroid sample linear float c,
+            linear linear float d);
+// CHECK-LABEL: FunctionDecl {{.*}} merged
+// CHECK: ParmVarDecl {{.*}} a 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 38
+// CHECK-NOT: HLSLInterpolationModifierAttr
+// CHECK: ParmVarDecl {{.*}} b 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 38
+// CHECK-NOT: HLSLInterpolationModifierAttr
+// CHECK: ParmVarDecl {{.*}} c 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 58
+// CHECK-NOT: HLSLInterpolationModifierAttr
+// CHECK: ParmVarDecl {{.*}} d 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2
+// CHECK-NOT: HLSLInterpolationModifierAttr
+
+struct Fields {
+  centroid float x;
+  float noperspective y;
+};
+// CHECK-LABEL: CXXRecordDecl {{.*}} struct Fields definition
+// CHECK: FieldDecl {{.*}} x 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 16
+// CHECK: FieldDecl {{.*}} y 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 4
+
+linear float result();
+// CHECK-LABEL: FunctionDecl {{.*}} result
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2
+
+// No entry point is needed to preserve modifiers on a dependent declaration.
+template<typename T> void dependent(sample T x) {}
+void instantiate() { dependent(1.0f); }
+// CHECK-LABEL: FunctionTemplateDecl {{.*}} dependent
+// CHECK: ParmVarDecl {{.*}} x 'T'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32
+// CHECK: FunctionDecl {{.*}} dependent 'void (float)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} x 'float'
+// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32
diff --git a/clang/test/SemaHLSL/interpolation-modifiers.hlsl 
b/clang/test/SemaHLSL/interpolation-modifiers.hlsl
new file mode 100644
index 0000000000000..b278eba8d26a7
--- /dev/null
+++ b/clang/test/SemaHLSL/interpolation-modifiers.hlsl
@@ -0,0 +1,185 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-finclude-default-header -verify %s
+
+// Each keyword is incompatible with nointerpolation, in either order.
+void conflicts(
+    // expected-error@+2 {{'nointerpolation' cannot be combined}}
+    // expected-note@+1 {{conflicting attribute is here}}
+    nointerpolation linear float a,
+    // expected-error@+2 {{'nointerpolation' cannot be combined}}
+    // expected-note@+1 {{conflicting attribute is here}}
+    centroid nointerpolation float b,
+    // expected-error@+2 {{'nointerpolation' cannot be combined}}
+    // expected-note@+1 {{conflicting attribute is here}}
+    nointerpolation noperspective float c,
+    // expected-error@+2 {{'nointerpolation' cannot be combined}}
+    // expected-note@+1 {{conflicting attribute is here}}
+    sample nointerpolation float d,
+    // expected-error@+2 {{'nointerpolation' cannot be combined}}
+    // expected-note@+1 {{conflicting attribute is here}}
+    nointerpolation center float e);
+
+void duplicates(
+    // expected-warning@+1 {{duplicate interpolation modifier 
'nointerpolation'}}
+    nointerpolation nointerpolation float a,
+    // expected-warning@+1 {{duplicate interpolation modifier 'linear'}}
+    linear linear float b,
+    // expected-warning@+1 {{duplicate interpolation modifier 'centroid'}}
+    centroid centroid float c,
+    // expected-warning@+1 {{duplicate interpolation modifier 'noperspective'}}
+    noperspective noperspective float d,
+    // expected-warning@+1 {{duplicate interpolation modifier 'sample'}}
+    sample sample float e,
+    // expected-warning@+1 {{duplicate interpolation modifier 'center'}}
+    center center float f);
+
+void locations(
+    // expected-warning@+1 {{interpolation modifier 'centroid' overrides 
'center'}}
+    center centroid float a,
+    // expected-warning@+1 {{interpolation modifier 'centroid' overrides 
'center'}}
+    centroid center float b,
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'center'}}
+    center sample float c,
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'center'}}
+    sample center float d,
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    centroid sample float e,
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    sample centroid float f);
+
+// With all three locations, compare the new keyword against the previous
+// highest-precedence location, regardless of keyword order.
+void all_locations(
+    // expected-warning@+2 {{interpolation modifier 'centroid' overrides 
'center'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    center centroid sample float a,
+    // expected-warning@+2 {{interpolation modifier 'sample' overrides 
'center'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    center sample centroid float b,
+    // expected-warning@+2 {{interpolation modifier 'centroid' overrides 
'center'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    centroid center sample float c,
+    // expected-warning@+2 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'center'}}
+    centroid sample center float d,
+    // expected-warning@+2 {{interpolation modifier 'sample' overrides 
'center'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    sample center centroid float e,
+    // expected-warning@+2 {{interpolation modifier 'sample' overrides 
'centroid'}}
+    // expected-warning@+1 {{interpolation modifier 'sample' overrides 
'center'}}
+    sample centroid center float f);
+
+// Interpolation modes do not imply an explicit sampling location, so combining
+// them with a location must not produce an override warning in either order.
+void implicit_locations(linear centroid float a, centroid linear float b,
+                        noperspective sample float c, sample noperspective 
float d,
+                        linear center float e, center linear float f);
+
+// Type restrictions apply to pixel shader inputs, not arbitrary declarations.
+[shader("pixel")]
+float4 invalid_types(
+    // expected-error@+1 {{cannot be used with type 'int'}}
+    linear int a : A,
+    // expected-error@+1 {{cannot be used with type 'uint'}}
+    centroid uint2 b : B,
+    // expected-error@+1 {{cannot be used with type 'bool'}}
+    noperspective bool c : C,
+    // expected-error@+1 {{cannot be used with type 'double'}}
+    sample double d : D,
+    // expected-error@+1 {{cannot be used with type 'int'}}
+    center int e[2][3] : E,
+    // expected-error@+1 {{cannot be used with type 'double'}}
+    linear double2x2 f : F,
+    // expected-error@+1 {{cannot be used with type 'int'}}
+    linear in int g : G) : SV_Target {
+  return 0;
+}
+
+[shader("pixel")]
+float4 valid_types(nointerpolation int a : A, nointerpolation bool2 b : B,
+                   nointerpolation double c : C, nointerpolation int d[2] : D,
+                   nointerpolation double2x2 e : E, linear float f : F,
+                   centroid half2 g : G, sample float2x2 h : H) : SV_Target {
+  return 0;
+}
+
+// The input side of an inout parameter also needs interpolation validation.
+// SV_Target currently rejects integer types independently of interpolation.
+[shader("pixel")]
+// expected-error@+2 {{cannot be used with type 'uint'}}
+// expected-error@+1 {{attribute 'SV_Target' only applies to a field or 
parameter of type}}
+void invalid_inout(linear inout uint a : SV_Target) {
+  a = 0;
+}
+
+struct Position {
+  // expected-note@+1 {{conflicting attribute is here}}
+  float4 p : SV_Position;
+};
+[shader("pixel")]
+// expected-error@+1 {{'nointerpolation' cannot be used on SV_Position}}
+float4 inherited_position(nointerpolation Position p) : SV_Target {
+  return 0;
+}
+[shader("pixel")]
+// expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}}
+// expected-note@+1 {{conflicting attribute is here}}
+float4 direct_position(nointerpolation float4 p : SV_Position) : SV_Target {
+  return 0;
+}
+
+struct Inner {
+  int value;
+};
+struct Outer {
+  Inner inner;
+};
+[shader("pixel")]
+// expected-error@+1 {{cannot be used with type 'int'}}
+float4 invalid_aggregate(linear Outer x : A) : SV_Target {
+  return 0;
+}
+
+// Field modifiers are checked even without an enclosing modifier, including
+// through unannotated aggregates. The field's declaration is not itself 
invalid.
+struct FieldModifiers {
+  // expected-error@+1 {{cannot be used with type 'uint'}}
+  sample uint value;
+};
+struct FieldOuter {
+  FieldModifiers inner;
+};
+[shader("pixel")]
+float4 invalid_field_modifier(FieldOuter x : A) : SV_Target { return 0; }
+[shader("vertex")]
+float4 valid_field_modifier(FieldOuter x : A) : SV_Position { return 0; }
+
+struct FlatPosition {
+  // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}}
+  // expected-note@+1 {{conflicting attribute is here}}
+  nointerpolation float4 p : SV_Position;
+};
+[shader("pixel")]
+float4 invalid_position_field(FlatPosition p) : SV_Target { return 0; }
+
+// An inner modifier replaces the entire inherited set, not just one keyword.
+struct Overrides {
+  nointerpolation int i;
+  linear float f;
+};
+[shader("pixel")]
+float4 valid_aggregate(sample Overrides x : A) : SV_Target { return 0; }
+struct PositionOverride {
+  linear float4 p : SV_Position;
+};
+[shader("pixel")]
+float4 valid_position(nointerpolation PositionOverride p) : SV_Target { return 
0; }
+
+// Subject restrictions also apply outside entry points.
+// expected-error@+1 {{'linear' attribute only applies to parameters, 
non-static data members, and functions}}
+linear float global;
+// expected-error@+1 {{'sample' attribute only applies to parameters, 
non-static data members, and functions}}
+typedef sample float Alias;
+void local() {
+  // expected-error@+1 {{'centroid' attribute only applies to parameters, 
non-static data members, and functions}}
+  centroid float value;
+}
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h 
b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 975fc2032d46e..ceb40c806c958 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -49,6 +49,37 @@ enum class IOType {
   LLVM_MARK_AS_BITMASK_ENUM(PatchConstantOrPrimitive),
 };
 
+// Keywords written on a declaration, before sampling-location precedence and
+// stage/type defaults are applied.
+enum class InterpolationModifier : uint32_t {
+  None = 0,
+  NoInterpolation = 1 << 0,
+  Linear = 1 << 1,
+  NoPerspective = 1 << 2,
+  // Sampling-location bits are ordered by increasing precedence for min/max.
+  Center = 1 << 3,
+  Centroid = 1 << 4,
+  Sample = 1 << 5,
+  LLVM_MARK_AS_BITMASK_ENUM(Sample),
+};
+
+static_assert(InterpolationModifier::None < InterpolationModifier::Center &&
+                  InterpolationModifier::Center <
+                      InterpolationModifier::Centroid &&
+                  InterpolationModifier::Centroid <
+                      InterpolationModifier::Sample,
+              "sampling-location bits must increase in precedence");
+
+// Select the highest-precedence sampling-location bit (sample > centroid >
+// center), ignoring other modifiers. Returns None if no location is explicit,
+// keeping it distinct from Center so diagnostics can detect location 
overrides.
+LLVM_ABI InterpolationModifier
+getInterpolationSamplingLocation(InterpolationModifier Modifiers);
+
+// Returns Invalid for nointerpolation combined with any other modifier.
+LLVM_ABI dxbc::PSV::InterpolationMode
+getInterpolationMode(InterpolationModifier Modifiers);
+
 enum class SemanticInterpretation {
   Invalid,
   NotAllocated,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp 
b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index efc84447a45f9..4bdef5bd02ffa 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -19,10 +19,12 @@
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Type.h"
 #include "llvm/Support/ErrorHandling.h"
+#include <algorithm>
 #include <cassert>
 
 using namespace llvm;
 using namespace llvm::hlsl;
+using llvm::dxbc::PSV::InterpolationMode;
 
 namespace {
 
@@ -46,6 +48,39 @@ Expected<uint64_t> extractInt(const MDNode *Node, unsigned 
OpId) {
 }
 } // namespace
 
+InterpolationModifier
+hlsl::getInterpolationSamplingLocation(InterpolationModifier Modifiers) {
+  return std::max({Modifiers & InterpolationModifier::Center,
+                   Modifiers & InterpolationModifier::Centroid,
+                   Modifiers & InterpolationModifier::Sample});
+}
+
+dxbc::PSV::InterpolationMode
+hlsl::getInterpolationMode(InterpolationModifier Modifiers) {
+  if (Modifiers == InterpolationModifier::None)
+    return InterpolationMode::Undefined;
+  if (any(Modifiers & InterpolationModifier::NoInterpolation))
+    return Modifiers == InterpolationModifier::NoInterpolation
+               ? InterpolationMode::Constant
+               : InterpolationMode::Invalid;
+
+  bool NoPerspective = any(Modifiers & InterpolationModifier::NoPerspective);
+  switch (getInterpolationSamplingLocation(Modifiers)) {
+  case InterpolationModifier::Sample:
+    return NoPerspective ? InterpolationMode::LinearNoperspectiveSample
+                         : InterpolationMode::LinearSample;
+  case InterpolationModifier::Centroid:
+    return NoPerspective ? InterpolationMode::LinearNoperspectiveCentroid
+                         : InterpolationMode::LinearCentroid;
+  case InterpolationModifier::Center:
+  case InterpolationModifier::None:
+    return NoPerspective ? InterpolationMode::LinearNoperspective
+                         : InterpolationMode::Linear;
+  default:
+    llvm_unreachable("invalid interpolation sampling location");
+  }
+}
+
 dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) {
   if (!SemanticName.consume_front_insensitive("SV_"))
     return dxbc::PSV::SemanticKind::Arbitrary;
diff --git a/llvm/unittests/Frontend/CMakeLists.txt 
b/llvm/unittests/Frontend/CMakeLists.txt
index 2373ae29e0599..2f0052b5eb601 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -15,6 +15,7 @@ set(LLVM_LINK_COMPONENTS
 add_llvm_unittest(LLVMFrontendTests
   EnumSetTest.cpp
   HLSLBindingTest.cpp
+  HLSLInterpolationTest.cpp
   HLSLRootSignatureDumpTest.cpp
   HLSLSemanticStagesTest.cpp
   HLSLSemanticSignatureMetadataTest.cpp
diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
new file mode 100644
index 0000000000000..354475c5a7e5f
--- /dev/null
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -0,0 +1,72 @@
+//===- HLSLInterpolationTest.cpp 
------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/HLSL/SemanticSignatures.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::hlsl;
+using Mod = InterpolationModifier;
+using InterpMode = dxbc::PSV::InterpolationMode;
+
+TEST(HLSLInterpolationTest, AllModifierSets) {
+  // Cover every subset, including redundant linear/center modifiers and all
+  // combinations of competing sampling locations.
+  for (unsigned Mask = 0; Mask != 64; ++Mask) {
+    SCOPED_TRACE(Mask);
+    auto Modifiers = static_cast<Mod>(Mask);
+    InterpMode Expected;
+    if (Modifiers == Mod::None)
+      Expected = InterpMode::Undefined;
+    else if (Modifiers == Mod::NoInterpolation)
+      Expected = InterpMode::Constant;
+    else if (any(Modifiers & Mod::NoInterpolation))
+      Expected = InterpMode::Invalid;
+    else {
+      static constexpr InterpMode Modes[2][3] = {
+          {InterpMode::Linear, InterpMode::LinearCentroid,
+           InterpMode::LinearSample},
+          {InterpMode::LinearNoperspective,
+           InterpMode::LinearNoperspectiveCentroid,
+           InterpMode::LinearNoperspectiveSample}};
+      unsigned Location = any(Modifiers & Mod::Sample)     ? 2
+                          : any(Modifiers & Mod::Centroid) ? 1
+                                                           : 0;
+      Expected = Modes[any(Modifiers & Mod::NoPerspective)][Location];
+    }
+    EXPECT_EQ(getInterpolationMode(Modifiers), Expected);
+  }
+}
+
+TEST(HLSLInterpolationTest, SamplingLocations) {
+  const struct {
+    Mod Modifiers;
+    Mod Expected;
+  } Cases[] = {
+      {Mod::None, Mod::None},
+      {Mod::Center, Mod::Center},
+      {Mod::Centroid, Mod::Centroid},
+      {Mod::Center | Mod::Centroid, Mod::Centroid},
+      {Mod::Sample, Mod::Sample},
+      {Mod::Center | Mod::Sample, Mod::Sample},
+      {Mod::Centroid | Mod::Sample, Mod::Sample},
+      {Mod::Center | Mod::Centroid | Mod::Sample, Mod::Sample},
+  };
+  // Non-location keywords must not imply an explicit center modifier or change
+  // sampling-location precedence, even in invalid interpolation combinations.
+  for (const auto &Case : Cases)
+    for (Mod Other :
+         {Mod::None, Mod::NoInterpolation, Mod::Linear, Mod::NoPerspective,
+          Mod::Linear | Mod::NoPerspective, Mod::NoInterpolation | Mod::Linear,
+          Mod::NoInterpolation | Mod::NoPerspective,
+          Mod::NoInterpolation | Mod::Linear | Mod::NoPerspective}) {
+      Mod Modifiers = Case.Modifiers | Other;
+      SCOPED_TRACE(static_cast<unsigned>(Modifiers));
+      EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected);
+    }
+}

>From 6584f4d6fea5d13d19f143ce261e24b76784fe3b Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Fri, 18 Sep 2026 17:22:31 +0000
Subject: [PATCH 03/10] lower interpolation modifiers to semantic signature
 metadata

---
 clang/include/clang/Basic/AttrDocs.td         |  5 +
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 34 +++++--
 clang/lib/CodeGen/CGHLSLRuntime.h             |  7 +-
 .../semantics/interpolation-aggregate.hlsl    | 45 +++++++++
 .../semantics/interpolation-no-entry.hlsl     |  0
 .../CodeGenHLSL/semantics/interpolation.hlsl  | 99 +++++++++++++++++++
 .../llvm/Frontend/HLSL/SemanticSignatures.h   |  7 ++
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 37 +++++++
 .../Frontend/HLSLInterpolationTest.cpp        | 55 +++++++++++
 9 files changed, 278 insertions(+), 11 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl
 create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl
 create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation.hlsl

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 5fe9d76ba967d..fa8e2c0502f66 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9399,6 +9399,11 @@ inherited from an enclosing declaration.
 `nointerpolation` cannot be combined with another interpolation modifier and
 cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point
 input components only support `nointerpolation`.
+
+Unqualified pixel shader inputs default to `linear` for floating-point
+components of at most 32 bits and `nointerpolation` otherwise. `SV_Position`
+uses the corresponding `noperspective` mode. Other shader stages and output
+signatures have no interpolation mode and are ignored.
   }];
 }
 
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 52fde18dc3ebd..a15b3490b8827 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1299,9 +1299,8 @@ static llvm::hlsl::SemanticSignatureElement 
createSemanticSignatureElement(
     SemanticIndices.push_back(FirstSemanticIndex + I);
 
   // The remaining members keep their default value and will be filled at a
-  // later stage, either during packing or analysis of usage
-  //
-  // FIXME #189762: Element.InterpMode is to be set
+  // later stage, either during packing or analysis of usage. Input
+  // interpolation is set by handleSemanticLoad after visiting each leaf.
   return llvm::hlsl::SemanticSignatureElement(
       SigId, Name, getSignatureComponentType(CGM, Shape.RowType),
       llvm::hlsl::getSemanticKind(Name), SemanticIndices,
@@ -1663,7 +1662,8 @@ CGHLSLRuntime::handleStructSemanticLoad(
     const clang::DeclaratorDecl *Decl,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-    SemanticSignatures &Signature) {
+    SemanticSignatures &Signature,
+    llvm::hlsl::InterpolationModifier Modifiers) {
   const llvm::StructType *ST = cast<StructType>(Type);
   const clang::RecordDecl *RD = Decl->getType()->getAsRecordDecl();
 
@@ -1674,7 +1674,7 @@ CGHLSLRuntime::handleStructSemanticLoad(
   for (unsigned I = 0; I < ST->getNumElements(); ++I) {
     auto [ChildValue, NextAttr] =
         handleSemanticLoad(B, FD, ST->getElementType(I), *FieldDecl, AttrBegin,
-                           AttrEnd, Signature);
+                           AttrEnd, Signature, Modifiers);
     AttrBegin = NextAttr;
     assert(ChildValue);
     Aggregate = B.CreateInsertValue(Aggregate, ChildValue, I);
@@ -1719,16 +1719,32 @@ CGHLSLRuntime::handleSemanticLoad(
     const clang::DeclaratorDecl *Decl,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-    SemanticSignatures &Signature) {
+    SemanticSignatures &Signature,
+    llvm::hlsl::InterpolationModifier Modifiers) {
   assert(AttrBegin != AttrEnd);
+  // Pass the enclosing declaration's mask down the traversal, replacing (not
+  // merging) it when an inner field has its own interpolation modifiers.
+  if (const auto *A = Decl->getAttr<HLSLInterpolationModifierAttr>())
+    Modifiers =
+        static_cast<llvm::hlsl::InterpolationModifier>(A->getModifiers());
   if (Type->isStructTy())
     return handleStructSemanticLoad(B, FD, Type, Decl, AttrBegin, AttrEnd,
-                                    Signature);
+                                    Signature, Modifiers);
 
   HLSLAppliedSemanticAttr *Attr = *AttrBegin;
   ++AttrBegin;
-  return std::make_pair(
-      handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature), AttrBegin);
+  size_t PreviousSize = Signature.size();
+  llvm::Value *Value =
+      handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature);
+  // Intrinsic-only system values and SPIR-V loads do not add DXIL signature
+  // elements. Outputs are not interpolated and retain Undefined.
+  if (Signature.size() != PreviousSize) {
+    auto &Element = Signature.back();
+    Element.InterpMode = llvm::hlsl::normalizeInterpolationMode(
+        llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType,
+        Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType());
+  }
+  return std::make_pair(Value, AttrBegin);
 }
 
 specific_attr_iterator<HLSLAppliedSemanticAttr>
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h 
b/clang/lib/CodeGen/CGHLSLRuntime.h
index f551bb006ea2c..90a4940a03832 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -256,7 +256,8 @@ class CGHLSLRuntime {
       const clang::DeclaratorDecl *Decl,
       specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
       specific_attr_iterator<HLSLAppliedSemanticAttr> end,
-      SemanticSignatures &Signature);
+      SemanticSignatures &Signature,
+      llvm::hlsl::InterpolationModifier Modifiers);
 
   specific_attr_iterator<HLSLAppliedSemanticAttr> handleStructSemanticStore(
       llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
@@ -270,7 +271,9 @@ class CGHLSLRuntime {
                      llvm::Type *Type, const clang::DeclaratorDecl *Decl,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> begin,
                      specific_attr_iterator<HLSLAppliedSemanticAttr> end,
-                     SemanticSignatures &Signature);
+                     SemanticSignatures &Signature,
+                     llvm::hlsl::InterpolationModifier Modifiers =
+                         llvm::hlsl::InterpolationModifier::None);
 
   specific_attr_iterator<HLSLAppliedSemanticAttr>
   handleSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD,
diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl
new file mode 100644
index 0000000000000..fbabd8d4704c7
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+struct Inner {
+  float inherited;
+  linear float overridden;
+};
+struct Outer {
+  Inner inherited;
+  sample noperspective Inner overridden;
+  float2 array[2][3];
+};
+
+[shader("pixel")]
+float4 main(nointerpolation Outer a : A, centroid Outer b : B,
+            Outer c : C) : SV_Target {
+  return 0;
+}
+
+// Each use of Outer inherits independently. Inner's explicit linear replaces
+// the entire inherited mask, and a field's modifier does not affect siblings.
+
+// CHECK-DAG: !{i32 0,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1,
+// CHECK-DAG: !{i32 1,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 2,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1,
+// CHECK-DAG: !{i32 3,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 4,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 6, i8 2,
+// CHECK-DAG: !{i32 5,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1,
+// CHECK-DAG: !{i32 6,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 7,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1,
+// CHECK-DAG: !{i32 8,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 9,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 6, i8 2,
+// CHECK-DAG: !{i32 10, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 11, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 12, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1,
+// CHECK-DAG: !{i32 13, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 14, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 6, i8 2,
+//                                                           ^ Interpolation 
mode.
+
+struct Position {
+  float4 p : SV_Position;
+};
+[shader("pixel")]
+float4 position(sample Position p) : SV_Target { return p.p; }
+// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 
1, i8 4,
+//                                                                    ^ 
Interpolation mode.
diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
new file mode 100644
index 0000000000000..88c46a15571cf
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
@@ -0,0 +1,99 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-finclude-default-header -Wno-ignored-attributes -emit-llvm 
-disable-llvm-passes -o - %s | FileCheck %s
+
+[shader("pixel")]
+float4 modes(float a : A,
+             nointerpolation float b : B,
+             linear float c : C,
+             centroid float d : D,
+             noperspective float e : E,
+             noperspective centroid float f : F,
+             sample float g : G,
+             noperspective sample float h : H,
+             center float i : I,
+             linear center float j : J,
+             linear centroid float k : K,
+             linear noperspective center float l : L,
+             linear noperspective centroid float m : M,
+             linear sample float n : N,
+             sample linear noperspective float o : O) : SV_Target {
+  return 0;
+}
+
+// CHECK-DAG: !{i32 0,  !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 1,  !"B", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1,
+// CHECK-DAG: !{i32 2,  !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 3,  !"D", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1,
+// CHECK-DAG: !{i32 4,  !"E", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 1,
+// CHECK-DAG: !{i32 5,  !"F", i32 9, i32 0, !{{[0-9]+}}, i32 5, i32 1, i8 1,
+// CHECK-DAG: !{i32 6,  !"G", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1,
+// CHECK-DAG: !{i32 7,  !"H", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1,
+// CHECK-DAG: !{i32 8,  !"I", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 9,  !"J", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 10, !"K", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1,
+// CHECK-DAG: !{i32 11, !"L", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 1,
+// CHECK-DAG: !{i32 12, !"M", i32 9, i32 0, !{{[0-9]+}}, i32 5, i32 1, i8 1,
+// CHECK-DAG: !{i32 13, !"N", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1,
+// CHECK-DAG: !{i32 14, !"O", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1,
+//                                                           ^ Interpolation 
mode.
+
+[shader("pixel")]
+sample float4 defaults(int a : INT, uint2 b : UINT, bool c : BOOL,
+                       double d : DOUBLE, nointerpolation float2 e : FLAT,
+                       half f : HALF) : SV_Target {
+  return 0;
+}
+// CHECK-DAG: !{i32 0, !"INT",    i32 4,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 1, !"UINT",   i32 5,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 2,
+// CHECK-DAG: !{i32 2, !"BOOL",   i32 {{[0-9]+}}, i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 3, !"DOUBLE", i32 10,         i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 4, !"FLAT",   i32 9,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 2,
+// CHECK-DAG: !{i32 5, !"HALF",   i32 {{[89]}},   i32 0, !{{[0-9]+}}, i32 2, 
i32 1, i8 1,
+//                                                                        ^ 
Interpolation mode.
+// Pixel outputs remain Undefined, including explicitly qualified returns.
+// CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, 
i8 4,
+//                                                                   ^ 
Interpolation mode.
+
+[shader("pixel")]
+float4 positions(float4 a : SV_Position0,
+                 centroid float4 b : SV_Position1,
+                 sample float4 c : SV_Position2,
+                 noperspective sample float4 d : SV_Position3) : SV_Target {
+  return a + b + c + d;
+}
+// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 4, i32 
1, i8 4,
+// CHECK-DAG: !{i32 1, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 5, i32 
1, i8 4,
+// CHECK-DAG: !{i32 2, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 
1, i8 4,
+// CHECK-DAG: !{i32 3, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 
1, i8 4,
+//                                                                    ^ 
Interpolation mode.
+
+[shader("pixel")]
+float4 precedence(center centroid float a : CENTER_CENTROID,
+                  centroid center float b : CENTROID_CENTER,
+                  center sample float c : CENTER_SAMPLE,
+                  sample center float d : SAMPLE_CENTER,
+                  centroid sample float e : CENTROID_SAMPLE,
+                  sample centroid float f : SAMPLE_CENTROID) : SV_Target {
+  return 0;
+}
+
+// Sampling-location precedence is independent of keyword order.
+
+// CHECK-DAG: !{i32 0, !"CENTER_CENTROID", i32 9, i32 0, !{{[0-9]+}}, i32 3, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 1, !"CENTROID_CENTER", i32 9, i32 0, !{{[0-9]+}}, i32 3, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 2, !"CENTER_SAMPLE",   i32 9, i32 0, !{{[0-9]+}}, i32 6, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 3, !"SAMPLE_CENTER",   i32 9, i32 0, !{{[0-9]+}}, i32 6, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 4, !"CENTROID_SAMPLE", i32 9, i32 0, !{{[0-9]+}}, i32 6, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 5, !"SAMPLE_CENTROID", i32 9, i32 0, !{{[0-9]+}}, i32 6, 
i32 1, i8 1,
+//                                                                        ^ 
Interpolation mode.
+
+[shader("vertex")]
+centroid float4 vertex(sample float4 a : VERTEX,
+                       nointerpolation uint b : SV_VertexID) : SV_Position {
+  return a;
+}
+
+// Non-pixel signatures remain Undefined even with explicit modifiers.
+// CHECK-DAG: !{i32 0, !"VERTEX",      i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 
1, i8 4,
+// CHECK-DAG: !{i32 1, !"SV_VertexID", i32 5, i32 1, !{{[0-9]+}}, i32 0, i32 
1, i8 1,
+// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 0, i32 
1, i8 4,
+//                                                                    ^ 
Interpolation mode.
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h 
b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index ceb40c806c958..d6d57edd6ffae 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -80,6 +80,13 @@ getInterpolationSamplingLocation(InterpolationModifier 
Modifiers);
 LLVM_ABI dxbc::PSV::InterpolationMode
 getInterpolationMode(InterpolationModifier Modifiers);
 
+// Apply component-type defaults, SV_Position's noperspective requirement, and
+// the shader-stage restriction. Only pixel shader inputs are interpolated;
+// callers should leave output signatures Undefined.
+LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode(
+    dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType,
+    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage);
+
 enum class SemanticInterpretation {
   Invalid,
   NotAllocated,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp 
b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 4bdef5bd02ffa..eccab31d272d4 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -81,6 +81,43 @@ hlsl::getInterpolationMode(InterpolationModifier Modifiers) {
   }
 }
 
+dxbc::PSV::InterpolationMode hlsl::normalizeInterpolationMode(
+    dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType,
+    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage) {
+  if (Stage != Triple::Pixel)
+    return InterpolationMode::Undefined;
+
+  if (Mode == InterpolationMode::Undefined) {
+    switch (CompType) {
+    case dxil::ElementType::F16:
+    case dxil::ElementType::F32:
+    case dxil::ElementType::SNormF16:
+    case dxil::ElementType::UNormF16:
+    case dxil::ElementType::SNormF32:
+    case dxil::ElementType::UNormF32:
+      Mode = InterpolationMode::Linear;
+      break;
+    default:
+      Mode = InterpolationMode::Constant;
+      break;
+    }
+  }
+
+  if (Kind == dxbc::PSV::SemanticKind::Position) {
+    switch (Mode) {
+    case InterpolationMode::Linear:
+      return InterpolationMode::LinearNoperspective;
+    case InterpolationMode::LinearCentroid:
+      return InterpolationMode::LinearNoperspectiveCentroid;
+    case InterpolationMode::LinearSample:
+      return InterpolationMode::LinearNoperspectiveSample;
+    default:
+      break;
+    }
+  }
+  return Mode;
+}
+
 dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) {
   if (!SemanticName.consume_front_insensitive("SV_"))
     return dxbc::PSV::SemanticKind::Arbitrary;
diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
index 354475c5a7e5f..d4c6bf623e5cf 100644
--- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -13,6 +13,8 @@ using namespace llvm;
 using namespace llvm::hlsl;
 using Mod = InterpolationModifier;
 using InterpMode = dxbc::PSV::InterpolationMode;
+using SemanticKind = dxbc::PSV::SemanticKind;
+using CompType = dxil::ElementType;
 
 TEST(HLSLInterpolationTest, AllModifierSets) {
   // Cover every subset, including redundant linear/center modifiers and all
@@ -70,3 +72,56 @@ TEST(HLSLInterpolationTest, SamplingLocations) {
       EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected);
     }
 }
+
+TEST(HLSLInterpolationTest, ComponentDefaults) {
+  for (CompType Type :
+       {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16,
+        CompType::SNormF32, CompType::UNormF32})
+    EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
+                                         SemanticKind::Arbitrary,
+                                         Triple::Pixel),
+              InterpMode::Linear);
+  for (CompType Type :
+       {CompType::I1, CompType::I16, CompType::U16, CompType::I32,
+        CompType::U32, CompType::I64, CompType::U64, CompType::F64,
+        CompType::SNormF64, CompType::UNormF64})
+    EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
+                                         SemanticKind::Arbitrary,
+                                         Triple::Pixel),
+              InterpMode::Constant);
+}
+
+TEST(HLSLInterpolationTest, PositionAndExplicitModes) {
+  constexpr InterpMode PositionModes[] = {
+      InterpMode::LinearNoperspective,
+      InterpMode::Constant,
+      InterpMode::LinearNoperspective,
+      InterpMode::LinearNoperspectiveCentroid,
+      InterpMode::LinearNoperspective,
+      InterpMode::LinearNoperspectiveCentroid,
+      InterpMode::LinearNoperspectiveSample,
+      InterpMode::LinearNoperspectiveSample,
+      InterpMode::Invalid};
+  for (unsigned I = 0; I != 9; ++I) {
+    auto Mode = static_cast<InterpMode>(I);
+    EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32,
+                                         SemanticKind::Position, 
Triple::Pixel),
+              PositionModes[I]);
+    if (Mode != InterpMode::Undefined)
+      EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32,
+                                           SemanticKind::Arbitrary,
+                                           Triple::Pixel),
+                Mode);
+  }
+}
+
+TEST(HLSLInterpolationTest, OtherStages) {
+  for (auto Stage :
+       {Triple::Vertex, Triple::Geometry, Triple::Hull, Triple::Domain,
+        Triple::Mesh, Triple::Compute, Triple::Amplification, Triple::Library})
+    for (unsigned I = 0; I != 9; ++I)
+      for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position})
+        EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I),
+                                             CompType::F32, Kind, Stage),
+                  InterpMode::Undefined);
+}

>From a15e027e388a807606c16960f1f8364bcbc04328 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Thu, 24 Sep 2026 16:33:32 +0000
Subject: [PATCH 04/10] lower for vertex outputs

---
 clang/include/clang/Basic/AttrDocs.td         |  18 ++--
 clang/lib/CodeGen/CGHLSLRuntime.cpp           |  31 ++++--
 clang/lib/CodeGen/CGHLSLRuntime.h             |   7 +-
 clang/lib/Sema/SemaHLSL.cpp                   |  18 ++--
 .../semantics/interpolation-vs-ps-link.hlsl   |  53 +++++++++
 .../CodeGenHLSL/semantics/interpolation.hlsl  |   5 +-
 .../semantics/semantic.output.hlsl            |   8 +-
 .../interpolation-modifiers-vertex.hlsl       | 102 ++++++++++++++++++
 .../llvm/Frontend/HLSL/SemanticSignatures.h   |   8 +-
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp |   5 +-
 .../Frontend/HLSLInterpolationTest.cpp        |  80 ++++++++------
 11 files changed, 266 insertions(+), 69 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl
 create mode 100644 clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index fa8e2c0502f66..38eca6ad292a2 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -9390,20 +9390,22 @@ def HLSLInterpolationModifierDocs : Documentation {
   let Content = [{
 The HLSL keywords `nointerpolation`, `linear`, `centroid`,
 `noperspective`, `sample`, and `center` control interpolation of pixel
-shader inputs.
+shader inputs and vertex shader outputs.
 
 When applied to an aggregate type, the modifier propagates recursively to every
 scalar or vector of the type. A modifier on an inner field overrides one
 inherited from an enclosing declaration.
 
-`nointerpolation` cannot be combined with another interpolation modifier and
-cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point
-input components only support `nointerpolation`.
+`nointerpolation` cannot be combined with another interpolation modifier.
+For pixel shader inputs and vertex shader outputs, it cannot be used on
+`SV_Position`, and integer, boolean, and 64-bit floating-point components only
+support `nointerpolation`.
 
-Unqualified pixel shader inputs default to `linear` for floating-point
-components of at most 32 bits and `nointerpolation` otherwise. `SV_Position`
-uses the corresponding `noperspective` mode. Other shader stages and output
-signatures have no interpolation mode and are ignored.
+Unqualified pixel shader inputs and vertex shader outputs default to `linear`
+for floating-point components of at most 32 bits and `nointerpolation` 
otherwise.
+`SV_Position` uses the corresponding `noperspective` mode. Vertex shader 
inputs,
+pixel shader outputs, and signatures in other shader stages have no
+interpolation mode; their interpolation modifiers are ignored.
   }];
 }
 
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index a15b3490b8827..11f4825a08a81 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1299,8 +1299,8 @@ static llvm::hlsl::SemanticSignatureElement 
createSemanticSignatureElement(
     SemanticIndices.push_back(FirstSemanticIndex + I);
 
   // The remaining members keep their default value and will be filled at a
-  // later stage, either during packing or analysis of usage. Input
-  // interpolation is set by handleSemanticLoad after visiting each leaf.
+  // later stage, either during packing or analysis of usage. Interpolation
+  // is set by the load/store traversal after visiting each leaf.
   return llvm::hlsl::SemanticSignatureElement(
       SigId, Name, getSignatureComponentType(CGM, Shape.RowType),
       llvm::hlsl::getSemanticKind(Name), SemanticIndices,
@@ -1690,7 +1690,8 @@ CGHLSLRuntime::handleStructSemanticStore(
     const clang::DeclaratorDecl *Decl,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-    SemanticSignatures &Signature) {
+    SemanticSignatures &Signature,
+    llvm::hlsl::InterpolationModifier Modifiers) {
 
   const llvm::StructType *ST = cast<StructType>(Source->getType());
 
@@ -1707,7 +1708,7 @@ CGHLSLRuntime::handleStructSemanticStore(
   for (unsigned I = 0; I < ST->getNumElements(); ++I, ++FieldDecl) {
     llvm::Value *Extract = B.CreateExtractValue(Source, I);
     AttrBegin = handleSemanticStore(B, FD, Extract, *FieldDecl, AttrBegin,
-                                    AttrEnd, Signature);
+                                    AttrEnd, Signature, Modifiers);
   }
 
   return AttrBegin;
@@ -1737,12 +1738,13 @@ CGHLSLRuntime::handleSemanticLoad(
   llvm::Value *Value =
       handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature);
   // Intrinsic-only system values and SPIR-V loads do not add DXIL signature
-  // elements. Outputs are not interpolated and retain Undefined.
+  // elements. Non-pixel inputs retain Undefined.
   if (Signature.size() != PreviousSize) {
     auto &Element = Signature.back();
     Element.InterpMode = llvm::hlsl::normalizeInterpolationMode(
         llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType,
-        Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType());
+        Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType(),
+        llvm::hlsl::IOType::In);
   }
   return std::make_pair(Value, AttrBegin);
 }
@@ -1753,15 +1755,28 @@ CGHLSLRuntime::handleSemanticStore(
     const clang::DeclaratorDecl *Decl,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
     specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-    SemanticSignatures &Signature) {
+    SemanticSignatures &Signature,
+    llvm::hlsl::InterpolationModifier Modifiers) {
   assert(AttrBegin != AttrEnd);
+  // An inner field overrides the enclosing return declaration's modifiers.
+  if (const auto *A = Decl->getAttr<HLSLInterpolationModifierAttr>())
+    Modifiers =
+        static_cast<llvm::hlsl::InterpolationModifier>(A->getModifiers());
   if (Source->getType()->isStructTy())
     return handleStructSemanticStore(B, FD, Source, Decl, AttrBegin, AttrEnd,
-                                     Signature);
+                                     Signature, Modifiers);
 
   HLSLAppliedSemanticAttr *Attr = *AttrBegin;
   ++AttrBegin;
+  size_t PreviousSize = Signature.size();
   handleScalarSemanticStore(B, FD, Source, Decl, Attr, Signature);
+  if (Signature.size() != PreviousSize) {
+    auto &Element = Signature.back();
+    Element.InterpMode = llvm::hlsl::normalizeInterpolationMode(
+        llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType,
+        Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType(),
+        llvm::hlsl::IOType::Out);
+  }
   return AttrBegin;
 }
 
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h 
b/clang/lib/CodeGen/CGHLSLRuntime.h
index 90a4940a03832..7aa97841331a6 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -264,7 +264,8 @@ class CGHLSLRuntime {
       const clang::DeclaratorDecl *Decl,
       specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
       specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-      SemanticSignatures &Signature);
+      SemanticSignatures &Signature,
+      llvm::hlsl::InterpolationModifier Modifiers);
 
   std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
   handleSemanticLoad(llvm::IRBuilder<> &B, const FunctionDecl *FD,
@@ -280,7 +281,9 @@ class CGHLSLRuntime {
                       llvm::Value *Source, const clang::DeclaratorDecl *Decl,
                       specific_attr_iterator<HLSLAppliedSemanticAttr> 
AttrBegin,
                       specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd,
-                      SemanticSignatures &Signature);
+                      SemanticSignatures &Signature,
+                      llvm::hlsl::InterpolationModifier Modifiers =
+                          llvm::hlsl::InterpolationModifier::None);
 
 public:
   CGHLSLRuntime(CodeGenModule &CGM) : CGM(CGM) {}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 17664ca7451df..690896b712ec9 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -910,8 +910,9 @@ bool SemaHLSL::checkInterpolationModifiers(
   if (!Semantic)
     Semantic = D->getAttr<HLSLParsedSemanticAttr>();
 
-  QualType T =
-      getASTContext().getBaseElementType(D->getType().getNonReferenceType());
+  const auto *FD = dyn_cast<FunctionDecl>(D);
+  QualType T = FD ? FD->getReturnType() : D->getType();
+  T = getASTContext().getBaseElementType(T.getNonReferenceType());
   if (T->isDependentType())
     return true;
   if (const auto *RT = T->getAs<RecordType>()) {
@@ -1197,9 +1198,10 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
     const auto *MA = Param->getAttr<HLSLParamModifierAttr>();
     SemanticContext &SC = MA && MA->isAnyOut() ? OutputSC : InputSC;
 
-    // Interpolation only applies to pixel shader inputs, including the input
-    // side of inout parameters. Other stages and output signatures ignore it.
-    if (ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn()) &&
+    // Interpolation applies to pixel inputs and vertex outputs, including the
+    // corresponding side of inout parameters.
+    if (((ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn())) ||
+         (ST == llvm::Triple::Vertex && MA && MA->isAnyOut())) &&
         !checkInterpolationModifiers(Param, nullptr, nullptr))
       FD->setInvalidDecl();
 
@@ -1213,8 +1215,12 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
   ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>();
   if (ActiveSemantic.Semantic)
     ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
-  if (!FD->getReturnType()->isVoidType())
+  if (!FD->getReturnType()->isVoidType()) {
+    if (ST == llvm::Triple::Vertex &&
+        !checkInterpolationModifiers(FD, nullptr, nullptr))
+      FD->setInvalidDecl();
     determineActiveSemantic(FD, FD, FD, ActiveSemantic, OutputSC);
+  }
 }
 
 void SemaHLSL::checkSemanticAnnotation(
diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl
new file mode 100644
index 0000000000000..1e118bc27fcdc
--- /dev/null
+++ b/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -emit-llvm 
-finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
+
+struct Nested {
+  float inherited : INHERIT;
+  linear float overrideMode : OVERRIDE;
+};
+
+struct Varyings {
+  float4 pos : SV_Position;
+  float value : LIN;
+  nointerpolation float flat : FLAT;
+  noperspective float screen : NOPERS;
+  centroid float cent : CENTRO;
+  uint face : FACEID;
+  nointerpolation Nested nested;
+};
+
+[shader("vertex")]
+Varyings vs(float4 pos : POSITION, float value : VAL) {
+  Varyings result;
+  result.pos = pos;
+  result.value = value;
+  result.flat = value;
+  result.screen = value;
+  result.cent = value;
+  result.face = 1;
+  result.nested.inherited = value;
+  result.nested.overrideMode = value;
+  return result;
+}
+
+[shader("pixel")]
+float4 ps(Varyings input) : SV_Target {
+  return float4(input.value, input.flat, input.screen,
+                input.nested.inherited + input.nested.overrideMode);
+}
+
+// Vertex outputs and pixel inputs must have identical packed signatures,
+// including their interpolation modes; only then can the shaders link.
+// CHECK: !dx.semantic.signatures = !{![[VS:[0-9]+]], ![[PS:[0-9]+]]}
+// CHECK-DAG: ![[VS]] = !{ptr @vs, ![[ATTR:[0-9]+]], ![[VARY:[0-9]+]]}
+// CHECK-DAG: ![[PS]] = !{ptr @ps, ![[VARY]], ![[TARGET:[0-9]+]]}
+// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 4, i32 
1, i8 4,
+// CHECK-DAG: !{i32 1, !"LIN", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1,
+// CHECK-DAG: !{i32 2, !"FLAT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1,
+// CHECK-DAG: !{i32 3, !"NOPERS", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 
1,
+// CHECK-DAG: !{i32 4, !"CENTRO", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 
1,
+// CHECK-DAG: !{i32 5, !"FACEID", i32 5, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 
1,
+// CHECK-DAG: !{i32 6, !"INHERIT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 
1,
+// CHECK-DAG: !{i32 7, !"OVERRIDE", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, 
i8 1,
+// Pixel outputs and vertex inputs do not use interpolation modes.
+// CHECK-DAG: !{i32 0, !"POSITION", i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 1, 
i8 4,
+// CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, 
i8 4,
diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
index 88c46a15571cf..1a242eab21edb 100644
--- a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
@@ -92,8 +92,9 @@ centroid float4 vertex(sample float4 a : VERTEX,
   return a;
 }
 
-// Non-pixel signatures remain Undefined even with explicit modifiers.
+// Vertex inputs remain Undefined; vertex outputs carry the interpolation mode
+// used to pack against pixel inputs. SV_Position is noperspective.
 // CHECK-DAG: !{i32 0, !"VERTEX",      i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 
1, i8 4,
 // CHECK-DAG: !{i32 1, !"SV_VertexID", i32 5, i32 1, !{{[0-9]+}}, i32 0, i32 
1, i8 1,
-// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 0, i32 
1, i8 4,
+// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 5, i32 
1, i8 4,
 //                                                                    ^ 
Interpolation mode.
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl 
b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
index 54e9aa83cda0b..9be9c6739112f 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl
@@ -58,10 +58,10 @@ S main() {
 // CHECK: !dx.semantic.signatures = !{![[#ENTRY_SIG:]]}
 // CHECK: ![[#ENTRY_SIG]] = !{ptr @main, null, ![[#OUTPUT_SIG:]]}
 // CHECK: ![[#OUTPUT_SIG]] = !{![[#A_SIG:]], ![[#B_SIG:]], ![[#D_SIG:]], 
![[#E_SIG:]]}
-// CHECK: ![[#A_SIG]] = !{i32 0, !"A", i32 9, i32 0, ![[#ZERO_INDEX:]], i32 0, 
i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0}
+// CHECK: ![[#A_SIG]] = !{i32 0, !"A", i32 9, i32 0, ![[#ZERO_INDEX:]], i32 2, 
i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0}
 // CHECK: ![[#ZERO_INDEX]] = !{i32 0}
-// CHECK: ![[#B_SIG]] = !{i32 1, !"B", i32 9, i32 0, ![[#ZERO_INDEX]], i32 0, 
i32 1, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0}
-// CHECK: ![[#D_SIG]] = !{i32 2, !"D", i32 9, i32 0, ![[#D_INDICES:]], i32 0, 
i32 5, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0}
+// CHECK: ![[#B_SIG]] = !{i32 1, !"B", i32 9, i32 0, ![[#ZERO_INDEX]], i32 2, 
i32 1, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0}
+// CHECK: ![[#D_SIG]] = !{i32 2, !"D", i32 9, i32 0, ![[#D_INDICES:]], i32 2, 
i32 5, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0}
 // CHECK: ![[#D_INDICES]] = !{i32 0, i32 1, i32 2, i32 3, i32 4}
-// CHECK: ![[#E_SIG]] = !{i32 3, !"E", i32 9, i32 0, ![[#E_INDICES:]], i32 0, 
i32 6, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0}
+// CHECK: ![[#E_SIG]] = !{i32 3, !"E", i32 9, i32 0, ![[#E_INDICES:]], i32 2, 
i32 6, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0}
 // CHECK: ![[#E_INDICES]] = !{i32 0, i32 1, i32 2, i32 3, i32 4, i32 5}
diff --git a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl 
b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl
new file mode 100644
index 0000000000000..fee5422635011
--- /dev/null
+++ b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl
@@ -0,0 +1,102 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library 
-finclude-default-header -verify %s
+
+// Vertex return declarations have the same interpolation restrictions as pixel
+// inputs. Check the return type, not the function type.
+[shader("vertex")]
+// expected-error@+1 {{cannot be used with type 'uint'}}
+linear uint invalid_integer() : VALUE { return 0; }
+[shader("vertex")]
+// expected-error@+1 {{cannot be used with type 'bool'}}
+centroid bool invalid_bool() : VALUE { return false; }
+[shader("vertex")]
+// expected-error@+1 {{cannot be used with type 'double'}}
+sample double invalid_double() : VALUE { return 0; }
+[shader("vertex")]
+// expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}}
+// expected-note@+1 {{conflicting attribute is here}}
+nointerpolation float4 invalid_position() : SV_Position { return 0; }
+
+// Unqualified aggregates must still validate modifiers on nested fields.
+struct InvalidField {
+  // expected-error@+1 {{cannot be used with type 'uint'}}
+  linear uint value : VALUE;
+};
+struct InvalidOuter {
+  float4 pos : SV_Position;
+  InvalidField inner;
+};
+[shader("vertex")]
+InvalidOuter invalid_nested_field() { return (InvalidOuter)0; }
+
+// A return modifier propagates through nested aggregates and array leaves.
+struct IntegerLeaf {
+  int values[2] : VALUE;
+};
+struct IntegerOuter {
+  IntegerLeaf inner;
+};
+[shader("vertex")]
+// expected-error@+1 {{cannot be used with type 'int'}}
+linear IntegerOuter invalid_inherited_type() { return (IntegerOuter)0; }
+
+struct PositionLeaf {
+  // expected-note@+1 {{conflicting attribute is here}}
+  float4 pos : SV_Position;
+};
+struct PositionOuter {
+  PositionLeaf inner;
+};
+[shader("vertex")]
+// expected-error@+1 {{'nointerpolation' cannot be used on SV_Position}}
+nointerpolation PositionOuter invalid_inherited_position() {
+  return (PositionOuter)0;
+}
+
+struct FlatPosition {
+  // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}}
+  // expected-note@+1 {{conflicting attribute is here}}
+  nointerpolation float4 pos : SV_Position;
+};
+[shader("vertex")]
+FlatPosition invalid_position_field() { return (FlatPosition)0; }
+
+// The output side of out and inout parameters also needs validation.
+[shader("vertex")]
+void invalid_output_parameters(
+    // expected-error@+1 {{cannot be used with type 'uint'}}
+    linear out uint a : A,
+    // expected-error@+1 {{cannot be used with type 'int'}}
+    sample inout int b : B) {
+  a = 0;
+}
+
+// Vertex inputs and non-entry declarations still ignore these restrictions.
+linear uint helper() { return 0; }
+[shader("vertex")]
+float4 valid_input(linear uint a : VALUE) : SV_Position { return 0; }
+
+[shader("vertex")]
+nointerpolation uint valid_integer() : VALUE { return 0; }
+[shader("vertex")]
+nointerpolation bool valid_bool() : VALUE { return false; }
+[shader("vertex")]
+nointerpolation double valid_double() : VALUE { return 0; }
+
+// Inner modifiers replace the entire inherited set, including the return's
+// modifier. Neither the integer nor the position should inherit sample/flat.
+struct OverrideLeaf {
+  nointerpolation uint value : VALUE;
+  linear float4 pos : SV_Position;
+};
+struct OverrideOuter {
+  nointerpolation OverrideLeaf inner;
+  float other : OTHER;
+};
+[shader("vertex")]
+sample OverrideOuter valid_overrides() { return (OverrideOuter)0; }
+
+[shader("vertex")]
+void valid_output_parameters(nointerpolation out uint a : A,
+                             nointerpolation inout int b : B) {
+  a = 0;
+}
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h 
b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index d6d57edd6ffae..f16d4b1de436f 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -80,12 +80,12 @@ getInterpolationSamplingLocation(InterpolationModifier 
Modifiers);
 LLVM_ABI dxbc::PSV::InterpolationMode
 getInterpolationMode(InterpolationModifier Modifiers);
 
-// Apply component-type defaults, SV_Position's noperspective requirement, and
-// the shader-stage restriction. Only pixel shader inputs are interpolated;
-// callers should leave output signatures Undefined.
+// Apply component-type defaults and SV_Position's noperspective requirement.
+// Vertex outputs carry interpolation modes so they pack compatibly with pixel
+// inputs; other signature points do not use interpolation modes.
 LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode(
     dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType,
-    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage);
+    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage, IOType IO);
 
 enum class SemanticInterpretation {
   Invalid,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp 
b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index eccab31d272d4..acade318f1ca5 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -83,8 +83,9 @@ hlsl::getInterpolationMode(InterpolationModifier Modifiers) {
 
 dxbc::PSV::InterpolationMode hlsl::normalizeInterpolationMode(
     dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType,
-    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage) {
-  if (Stage != Triple::Pixel)
+    dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage, IOType IO) {
+  if (!((Stage == Triple::Pixel && IO == IOType::In) ||
+        (Stage == Triple::Vertex && IO == IOType::Out)))
     return InterpolationMode::Undefined;
 
   if (Mode == InterpolationMode::Undefined) {
diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
index d4c6bf623e5cf..af8f79c052e3b 100644
--- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -74,21 +74,23 @@ TEST(HLSLInterpolationTest, SamplingLocations) {
 }
 
 TEST(HLSLInterpolationTest, ComponentDefaults) {
-  for (CompType Type :
-       {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16,
-        CompType::SNormF32, CompType::UNormF32})
-    EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
-                                         SemanticKind::Arbitrary,
-                                         Triple::Pixel),
-              InterpMode::Linear);
-  for (CompType Type :
-       {CompType::I1, CompType::I16, CompType::U16, CompType::I32,
-        CompType::U32, CompType::I64, CompType::U64, CompType::F64,
-        CompType::SNormF64, CompType::UNormF64})
-    EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
-                                         SemanticKind::Arbitrary,
-                                         Triple::Pixel),
-              InterpMode::Constant);
+  for (auto Stage : {Triple::Pixel, Triple::Vertex}) {
+    SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str());
+    IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out;
+    for (CompType Type :
+         {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16,
+          CompType::SNormF32, CompType::UNormF32})
+      EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
+                                           SemanticKind::Arbitrary, Stage, IO),
+                InterpMode::Linear);
+    for (CompType Type :
+         {CompType::I1, CompType::I16, CompType::U16, CompType::I32,
+          CompType::U32, CompType::I64, CompType::U64, CompType::F64,
+          CompType::SNormF64, CompType::UNormF64})
+      EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
+                                           SemanticKind::Arbitrary, Stage, IO),
+                InterpMode::Constant);
+  }
 }
 
 TEST(HLSLInterpolationTest, PositionAndExplicitModes) {
@@ -102,26 +104,38 @@ TEST(HLSLInterpolationTest, PositionAndExplicitModes) {
       InterpMode::LinearNoperspectiveSample,
       InterpMode::LinearNoperspectiveSample,
       InterpMode::Invalid};
-  for (unsigned I = 0; I != 9; ++I) {
-    auto Mode = static_cast<InterpMode>(I);
-    EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32,
-                                         SemanticKind::Position, 
Triple::Pixel),
-              PositionModes[I]);
-    if (Mode != InterpMode::Undefined)
+  for (auto Stage : {Triple::Pixel, Triple::Vertex}) {
+    SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str());
+    IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out;
+    for (unsigned I = 0; I != 9; ++I) {
+      SCOPED_TRACE(I);
+      auto Mode = static_cast<InterpMode>(I);
       EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32,
-                                           SemanticKind::Arbitrary,
-                                           Triple::Pixel),
-                Mode);
+                                           SemanticKind::Position, Stage, IO),
+                PositionModes[I]);
+      if (Mode != InterpMode::Undefined)
+        EXPECT_EQ(normalizeInterpolationMode(
+                      Mode, CompType::F32, SemanticKind::Arbitrary, Stage, IO),
+                  Mode);
+    }
   }
 }
 
-TEST(HLSLInterpolationTest, OtherStages) {
-  for (auto Stage :
-       {Triple::Vertex, Triple::Geometry, Triple::Hull, Triple::Domain,
-        Triple::Mesh, Triple::Compute, Triple::Amplification, Triple::Library})
-    for (unsigned I = 0; I != 9; ++I)
-      for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position})
-        EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I),
-                                             CompType::F32, Kind, Stage),
-                  InterpMode::Undefined);
+TEST(HLSLInterpolationTest, NonInterpolatedSignatures) {
+  for (auto Stage : {Triple::Pixel, Triple::Vertex, Triple::Geometry,
+                     Triple::Hull, Triple::Domain, Triple::Mesh,
+                     Triple::Compute, Triple::Amplification, Triple::Library}) 
{
+    SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str());
+    for (auto IO : {IOType::In, IOType::Out}) {
+      SCOPED_TRACE(static_cast<unsigned>(IO));
+      if ((Stage == Triple::Pixel && IO == IOType::In) ||
+          (Stage == Triple::Vertex && IO == IOType::Out))
+        continue;
+      for (unsigned I = 0; I != 9; ++I)
+        for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position})
+          EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I),
+                                               CompType::F32, Kind, Stage, IO),
+                    InterpMode::Undefined);
+    }
+  }
 }

>From 86cf88c55bce8747ea7c3133c649e546e110de39 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Tue, 22 Sep 2026 16:23:56 +0000
Subject: [PATCH 05/10] nfc: remove unused isHLSLQualifier

---
 clang/include/clang/Parse/Parser.h | 1 -
 clang/lib/Parse/ParseDecl.cpp      | 7 -------
 2 files changed, 8 deletions(-)

diff --git a/clang/include/clang/Parse/Parser.h 
b/clang/include/clang/Parse/Parser.h
index 6de876b151097..62ad82bd1cb73 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2379,7 +2379,6 @@ class Parser : public CodeCompletionHandler {
   void ParseOpenCLQualifiers(ParsedAttributes &Attrs);
   void ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs);
   void ParseCUDAFunctionAttributes(ParsedAttributes &attrs);
-  bool isHLSLQualifier(const Token &Tok) const;
   void ParseHLSLQualifiers(ParsedAttributes &Attrs);
 
   /// Parse a version number.
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 6fb777a9416bc..913cadd175fbc 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -1092,13 +1092,6 @@ void Parser::ParseOpenCLQualifiers(ParsedAttributes 
&Attrs) {
                Tok.getKind());
 }
 
-bool Parser::isHLSLQualifier(const Token &Tok) const {
-  return Tok.is(tok::kw_groupshared) || Tok.is(tok::kw_row_major) ||
-         Tok.is(tok::kw_column_major) ||
-         Tok.isOneOf(tok::kw_nointerpolation, tok::kw_linear, tok::kw_centroid,
-                     tok::kw_noperspective, tok::kw_sample, tok::kw_center);
-}
-
 void Parser::ParseHLSLQualifiers(ParsedAttributes &Attrs) {
   IdentifierInfo *AttrName = Tok.getIdentifierInfo();
   auto Kind = Tok.getKind();

>From 0528f31b4874128afb44b6bd9325318cd0085027 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Tue, 22 Sep 2026 16:30:38 +0000
Subject: [PATCH 06/10] removed unused file

---
 clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl

diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl
deleted file mode 100644
index e69de29bb2d1d..0000000000000

>From 94dae80279da473aa2b90b5405a4f8989def8826 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Tue, 22 Sep 2026 16:35:18 +0000
Subject: [PATCH 07/10] self-review: use %select for interpolation override
 diagnostics

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++-
 clang/lib/Sema/SemaHLSL.cpp                   | 22 ++++---------------
 2 files changed, 6 insertions(+), 19 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c84e9a18cadd6..c271ff92b12eb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13716,7 +13716,8 @@ def warn_hlsl_duplicate_interpolation : Warning<
   "duplicate interpolation modifier %0">,
   InGroup<DuplicateDeclSpecifier>;
 def warn_hlsl_interpolation_override : Warning<
-  "interpolation modifier '%0' overrides '%1'">,
+  "interpolation modifier '%select{centroid|sample}0' overrides "
+  "'%select{center|centroid}1'">,
   InGroup<IgnoredAttributes>;
 def err_hlsl_missing_semantic_annotation : Error<
   "semantic annotations must be present for all parameters of an entry "
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 690896b712ec9..ab5d4b31bccad 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -827,20 +827,6 @@ SemaHLSL::mergeParamModifierAttr(Decl *D, const 
AttributeCommonInfo &AL,
   return HLSLParamModifierAttr::Create(getASTContext(), AL);
 }
 
-static StringRef
-getInterpolationSamplingLocationName(InterpolationModifier Location) {
-  switch (Location) {
-  case InterpolationModifier::Sample:
-    return "sample";
-  case InterpolationModifier::Centroid:
-    return "centroid";
-  case InterpolationModifier::Center:
-    return "center";
-  default:
-    llvm_unreachable("expected an explicit interpolation sampling location");
-  }
-}
-
 void SemaHLSL::handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL) {
   InterpolationModifier Modifier;
   switch (static_cast<HLSLInterpolationModifierAttr::Spelling>(
@@ -887,10 +873,10 @@ void SemaHLSL::handleInterpolationModifierAttr(Decl *D, 
const ParsedAttr &AL) {
           llvm::hlsl::getInterpolationSamplingLocation(Modifier);
       if (any(OldLocation) && any(NewLocation)) {
         Diag(AL.getLoc(), diag::warn_hlsl_interpolation_override)
-            << getInterpolationSamplingLocationName(
-                   std::max(OldLocation, NewLocation))
-            << getInterpolationSamplingLocationName(
-                   std::min(OldLocation, NewLocation));
+            << (std::max(OldLocation, NewLocation) ==
+                InterpolationModifier::Sample)
+            << (std::min(OldLocation, NewLocation) ==
+                InterpolationModifier::Centroid);
       }
     }
     D->dropAttr<HLSLInterpolationModifierAttr>();

>From ee0621dd2315613346dcadc33817efd2b2c0fb83 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Thu, 24 Sep 2026 16:59:44 +0000
Subject: [PATCH 08/10] self-review: remove unsupported 64-bit signature test
 cases

---
 clang/test/CodeGenHLSL/semantics/interpolation.hlsl    |  9 ++++-----
 .../test/SemaHLSL/interpolation-modifiers-vertex.hlsl  |  5 -----
 clang/test/SemaHLSL/interpolation-modifiers.hlsl       | 10 ++++------
 llvm/unittests/Frontend/HLSLInterpolationTest.cpp      |  6 ++----
 4 files changed, 10 insertions(+), 20 deletions(-)

diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl 
b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
index 1a242eab21edb..5f39c2d2af8aa 100644
--- a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl
@@ -38,16 +38,15 @@ float4 modes(float a : A,
 
 [shader("pixel")]
 sample float4 defaults(int a : INT, uint2 b : UINT, bool c : BOOL,
-                       double d : DOUBLE, nointerpolation float2 e : FLAT,
-                       half f : HALF) : SV_Target {
+                       nointerpolation float2 e : FLAT, half f : HALF)
+    : SV_Target {
   return 0;
 }
 // CHECK-DAG: !{i32 0, !"INT",    i32 4,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
 // CHECK-DAG: !{i32 1, !"UINT",   i32 5,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 2,
 // CHECK-DAG: !{i32 2, !"BOOL",   i32 {{[0-9]+}}, i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
-// CHECK-DAG: !{i32 3, !"DOUBLE", i32 10,         i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 1,
-// CHECK-DAG: !{i32 4, !"FLAT",   i32 9,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 2,
-// CHECK-DAG: !{i32 5, !"HALF",   i32 {{[89]}},   i32 0, !{{[0-9]+}}, i32 2, 
i32 1, i8 1,
+// CHECK-DAG: !{i32 3, !"FLAT",   i32 9,          i32 0, !{{[0-9]+}}, i32 1, 
i32 1, i8 2,
+// CHECK-DAG: !{i32 4, !"HALF",   i32 {{[89]}},   i32 0, !{{[0-9]+}}, i32 2, 
i32 1, i8 1,
 //                                                                        ^ 
Interpolation mode.
 // Pixel outputs remain Undefined, including explicitly qualified returns.
 // CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, 
i8 4,
diff --git a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl 
b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl
index fee5422635011..997aef970d84f 100644
--- a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl
+++ b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl
@@ -9,9 +9,6 @@ linear uint invalid_integer() : VALUE { return 0; }
 // expected-error@+1 {{cannot be used with type 'bool'}}
 centroid bool invalid_bool() : VALUE { return false; }
 [shader("vertex")]
-// expected-error@+1 {{cannot be used with type 'double'}}
-sample double invalid_double() : VALUE { return 0; }
-[shader("vertex")]
 // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}}
 // expected-note@+1 {{conflicting attribute is here}}
 nointerpolation float4 invalid_position() : SV_Position { return 0; }
@@ -79,8 +76,6 @@ float4 valid_input(linear uint a : VALUE) : SV_Position { 
return 0; }
 nointerpolation uint valid_integer() : VALUE { return 0; }
 [shader("vertex")]
 nointerpolation bool valid_bool() : VALUE { return false; }
-[shader("vertex")]
-nointerpolation double valid_double() : VALUE { return 0; }
 
 // Inner modifiers replace the entire inherited set, including the return's
 // modifier. Neither the integer nor the position should inherit sample/flat.
diff --git a/clang/test/SemaHLSL/interpolation-modifiers.hlsl 
b/clang/test/SemaHLSL/interpolation-modifiers.hlsl
index b278eba8d26a7..7105a4ae6268d 100644
--- a/clang/test/SemaHLSL/interpolation-modifiers.hlsl
+++ b/clang/test/SemaHLSL/interpolation-modifiers.hlsl
@@ -83,12 +83,10 @@ float4 invalid_types(
     centroid uint2 b : B,
     // expected-error@+1 {{cannot be used with type 'bool'}}
     noperspective bool c : C,
-    // expected-error@+1 {{cannot be used with type 'double'}}
-    sample double d : D,
     // expected-error@+1 {{cannot be used with type 'int'}}
     center int e[2][3] : E,
-    // expected-error@+1 {{cannot be used with type 'double'}}
-    linear double2x2 f : F,
+    // expected-error@+1 {{cannot be used with type 'int'}}
+    linear int2x2 f : F,
     // expected-error@+1 {{cannot be used with type 'int'}}
     linear in int g : G) : SV_Target {
   return 0;
@@ -96,8 +94,8 @@ float4 invalid_types(
 
 [shader("pixel")]
 float4 valid_types(nointerpolation int a : A, nointerpolation bool2 b : B,
-                   nointerpolation double c : C, nointerpolation int d[2] : D,
-                   nointerpolation double2x2 e : E, linear float f : F,
+                   nointerpolation int d[2] : D,
+                   nointerpolation int2x2 e : E, linear float f : F,
                    centroid half2 g : G, sample float2x2 h : H) : SV_Target {
   return 0;
 }
diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
index af8f79c052e3b..1ff2b32b50132 100644
--- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -83,10 +83,8 @@ TEST(HLSLInterpolationTest, ComponentDefaults) {
       EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
                                            SemanticKind::Arbitrary, Stage, IO),
                 InterpMode::Linear);
-    for (CompType Type :
-         {CompType::I1, CompType::I16, CompType::U16, CompType::I32,
-          CompType::U32, CompType::I64, CompType::U64, CompType::F64,
-          CompType::SNormF64, CompType::UNormF64})
+    for (CompType Type : {CompType::I1, CompType::I16, CompType::U16,
+                          CompType::I32, CompType::U32})
       EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type,
                                            SemanticKind::Arbitrary, Stage, IO),
                 InterpMode::Constant);

>From 0db852a57b05fd90b119c364b238ecb0991bd936 Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Thu, 24 Sep 2026 17:08:31 +0000
Subject: [PATCH 09/10] review: use structured bindings

---
 llvm/unittests/Frontend/HLSLInterpolationTest.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
index 1ff2b32b50132..f875efda6219d 100644
--- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -61,15 +61,15 @@ TEST(HLSLInterpolationTest, SamplingLocations) {
   };
   // Non-location keywords must not imply an explicit center modifier or change
   // sampling-location precedence, even in invalid interpolation combinations.
-  for (const auto &Case : Cases)
+  for (const auto &[CaseModifiers, Expected] : Cases)
     for (Mod Other :
          {Mod::None, Mod::NoInterpolation, Mod::Linear, Mod::NoPerspective,
           Mod::Linear | Mod::NoPerspective, Mod::NoInterpolation | Mod::Linear,
           Mod::NoInterpolation | Mod::NoPerspective,
           Mod::NoInterpolation | Mod::Linear | Mod::NoPerspective}) {
-      Mod Modifiers = Case.Modifiers | Other;
+      Mod Modifiers = CaseModifiers | Other;
       SCOPED_TRACE(static_cast<unsigned>(Modifiers));
-      EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected);
+      EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Expected);
     }
 }
 

>From 958487f41c243d129311fa4157955ee9878dbb8a Mon Sep 17 00:00:00 2001
From: Finn Plummer <[email protected]>
Date: Thu, 24 Sep 2026 17:09:16 +0000
Subject: [PATCH 10/10] review: remove hard-coded bounds

---
 llvm/unittests/Frontend/HLSLInterpolationTest.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp 
b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
index f875efda6219d..d73de2cb8cb11 100644
--- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
+++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp
@@ -6,6 +6,7 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Frontend/HLSL/SemanticSignatures.h"
 #include "gtest/gtest.h"
 
@@ -105,12 +106,12 @@ TEST(HLSLInterpolationTest, PositionAndExplicitModes) {
   for (auto Stage : {Triple::Pixel, Triple::Vertex}) {
     SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str());
     IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out;
-    for (unsigned I = 0; I != 9; ++I) {
+    for (auto [I, Expected] : llvm::enumerate(PositionModes)) {
       SCOPED_TRACE(I);
       auto Mode = static_cast<InterpMode>(I);
       EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32,
                                            SemanticKind::Position, Stage, IO),
-                PositionModes[I]);
+                Expected);
       if (Mode != InterpMode::Undefined)
         EXPECT_EQ(normalizeInterpolationMode(
                       Mode, CompType::F32, SemanticKind::Arbitrary, Stage, IO),

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to