[clang] [llvm] [HLSL] Add handling of Interpolation Modifiers (PR #225452)
Finn Plummer via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 7 08:00:58 PDT 2026
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 <mail at inbelic.dev>
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 at +1 {{passing 'int64_t' (aka 'long') to parameter of incompatible type 'int'}}
>From e90131695887981fcd703fb784d334afaae6e243 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
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 at +2 {{duplicate interpolation modifier 'linear'}}
+// duplicate-warning at +1 {{duplicate interpolation modifier 'linear'}}
+void duplicates(linear linear float x);
+
+// expected-warning at +2 {{interpolation modifier 'sample' overrides 'center'}}
+// override-warning at +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 at +2 {{'nointerpolation' cannot be combined}}
+ // expected-note at +1 {{conflicting attribute is here}}
+ nointerpolation linear float a,
+ // expected-error at +2 {{'nointerpolation' cannot be combined}}
+ // expected-note at +1 {{conflicting attribute is here}}
+ centroid nointerpolation float b,
+ // expected-error at +2 {{'nointerpolation' cannot be combined}}
+ // expected-note at +1 {{conflicting attribute is here}}
+ nointerpolation noperspective float c,
+ // expected-error at +2 {{'nointerpolation' cannot be combined}}
+ // expected-note at +1 {{conflicting attribute is here}}
+ sample nointerpolation float d,
+ // expected-error at +2 {{'nointerpolation' cannot be combined}}
+ // expected-note at +1 {{conflicting attribute is here}}
+ nointerpolation center float e);
+
+void duplicates(
+ // expected-warning at +1 {{duplicate interpolation modifier 'nointerpolation'}}
+ nointerpolation nointerpolation float a,
+ // expected-warning at +1 {{duplicate interpolation modifier 'linear'}}
+ linear linear float b,
+ // expected-warning at +1 {{duplicate interpolation modifier 'centroid'}}
+ centroid centroid float c,
+ // expected-warning at +1 {{duplicate interpolation modifier 'noperspective'}}
+ noperspective noperspective float d,
+ // expected-warning at +1 {{duplicate interpolation modifier 'sample'}}
+ sample sample float e,
+ // expected-warning at +1 {{duplicate interpolation modifier 'center'}}
+ center center float f);
+
+void locations(
+ // expected-warning at +1 {{interpolation modifier 'centroid' overrides 'center'}}
+ center centroid float a,
+ // expected-warning at +1 {{interpolation modifier 'centroid' overrides 'center'}}
+ centroid center float b,
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'center'}}
+ center sample float c,
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'center'}}
+ sample center float d,
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'centroid'}}
+ centroid sample float e,
+ // expected-warning at +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 at +2 {{interpolation modifier 'centroid' overrides 'center'}}
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'centroid'}}
+ center centroid sample float a,
+ // expected-warning at +2 {{interpolation modifier 'sample' overrides 'center'}}
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'centroid'}}
+ center sample centroid float b,
+ // expected-warning at +2 {{interpolation modifier 'centroid' overrides 'center'}}
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'centroid'}}
+ centroid center sample float c,
+ // expected-warning at +2 {{interpolation modifier 'sample' overrides 'centroid'}}
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'center'}}
+ centroid sample center float d,
+ // expected-warning at +2 {{interpolation modifier 'sample' overrides 'center'}}
+ // expected-warning at +1 {{interpolation modifier 'sample' overrides 'centroid'}}
+ sample center centroid float e,
+ // expected-warning at +2 {{interpolation modifier 'sample' overrides 'centroid'}}
+ // expected-warning at +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 at +1 {{cannot be used with type 'int'}}
+ linear int a : A,
+ // expected-error at +1 {{cannot be used with type 'uint'}}
+ centroid uint2 b : B,
+ // expected-error at +1 {{cannot be used with type 'bool'}}
+ noperspective bool c : C,
+ // expected-error at +1 {{cannot be used with type 'double'}}
+ sample double d : D,
+ // expected-error at +1 {{cannot be used with type 'int'}}
+ center int e[2][3] : E,
+ // expected-error at +1 {{cannot be used with type 'double'}}
+ linear double2x2 f : F,
+ // expected-error at +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 at +2 {{cannot be used with type 'uint'}}
+// expected-error at +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 at +1 {{conflicting attribute is here}}
+ float4 p : SV_Position;
+};
+[shader("pixel")]
+// expected-error at +1 {{'nointerpolation' cannot be used on SV_Position}}
+float4 inherited_position(nointerpolation Position p) : SV_Target {
+ return 0;
+}
+[shader("pixel")]
+// expected-error at +2 {{'nointerpolation' cannot be used on SV_Position}}
+// expected-note at +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 at +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 at +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 at +2 {{'nointerpolation' cannot be used on SV_Position}}
+ // expected-note at +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 at +1 {{'linear' attribute only applies to parameters, non-static data members, and functions}}
+linear float global;
+// expected-error at +1 {{'sample' attribute only applies to parameters, non-static data members, and functions}}
+typedef sample float Alias;
+void local() {
+ // expected-error at +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 <mail at inbelic.dev>
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 <mail at inbelic.dev>
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 at +1 {{cannot be used with type 'uint'}}
+linear uint invalid_integer() : VALUE { return 0; }
+[shader("vertex")]
+// expected-error at +1 {{cannot be used with type 'bool'}}
+centroid bool invalid_bool() : VALUE { return false; }
+[shader("vertex")]
+// expected-error at +1 {{cannot be used with type 'double'}}
+sample double invalid_double() : VALUE { return 0; }
+[shader("vertex")]
+// expected-error at +2 {{'nointerpolation' cannot be used on SV_Position}}
+// expected-note at +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 at +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 at +1 {{cannot be used with type 'int'}}
+linear IntegerOuter invalid_inherited_type() { return (IntegerOuter)0; }
+
+struct PositionLeaf {
+ // expected-note at +1 {{conflicting attribute is here}}
+ float4 pos : SV_Position;
+};
+struct PositionOuter {
+ PositionLeaf inner;
+};
+[shader("vertex")]
+// expected-error at +1 {{'nointerpolation' cannot be used on SV_Position}}
+nointerpolation PositionOuter invalid_inherited_position() {
+ return (PositionOuter)0;
+}
+
+struct FlatPosition {
+ // expected-error at +2 {{'nointerpolation' cannot be used on SV_Position}}
+ // expected-note at +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 at +1 {{cannot be used with type 'uint'}}
+ linear out uint a : A,
+ // expected-error at +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 <mail at inbelic.dev>
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 <mail at inbelic.dev>
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 <mail at inbelic.dev>
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 <mail at inbelic.dev>
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 at +1 {{cannot be used with type 'bool'}}
centroid bool invalid_bool() : VALUE { return false; }
[shader("vertex")]
-// expected-error at +1 {{cannot be used with type 'double'}}
-sample double invalid_double() : VALUE { return 0; }
-[shader("vertex")]
// expected-error at +2 {{'nointerpolation' cannot be used on SV_Position}}
// expected-note at +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 at +1 {{cannot be used with type 'bool'}}
noperspective bool c : C,
- // expected-error at +1 {{cannot be used with type 'double'}}
- sample double d : D,
// expected-error at +1 {{cannot be used with type 'int'}}
center int e[2][3] : E,
- // expected-error at +1 {{cannot be used with type 'double'}}
- linear double2x2 f : F,
+ // expected-error at +1 {{cannot be used with type 'int'}}
+ linear int2x2 f : F,
// expected-error at +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 <mail at inbelic.dev>
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 <mail at inbelic.dev>
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),
More information about the cfe-commits
mailing list