https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/225452
>From 6c97763aacf05b86ff1bdb2767d5bbe60331d47e Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Fri, 18 Sep 2026 17:22:07 +0000 Subject: [PATCH 01/10] parse interpolation modifier keywords --- clang/include/clang/Basic/Attr.td | 8 ++++++ clang/include/clang/Basic/TokenKinds.def | 6 ++++ clang/lib/Parse/ParseDecl.cpp | 28 ++++++++++++++++++- clang/lib/Parse/ParseTentative.cpp | 8 +++++- .../ParserHLSL/interpolation-keywords.hlsl | 6 ++++ .../BuiltIns/resource_load-errors.hlsl | 4 +-- 6 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 clang/test/ParserHLSL/interpolation-keywords.hlsl diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index b9eb41654a81b..5e7dd712b0aae 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -5321,6 +5321,14 @@ def HLSLGroupSharedAddressSpace : TypeAttr { let Documentation = [HLSLGroupSharedAddressSpaceDocs]; } +// Parse interpolation modifiers without attaching them to declarations yet. +def HLSLInterpolationModifier : IgnoredAttr { + let Spellings = [CustomKeyword<"nointerpolation">, CustomKeyword<"linear">, + CustomKeyword<"centroid">, CustomKeyword<"noperspective">, + CustomKeyword<"sample">, CustomKeyword<"center">]; + let LangOpts = [HLSL]; +} + def HLSLParamModifier : ParameterABIAttr { let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">]; let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>, diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index dc9c7d8109467..153caa568cd36 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -544,6 +544,12 @@ KEYWORD(__noinline__ , KEYCUDA) KEYWORD(cbuffer , KEYHLSL) KEYWORD(tbuffer , KEYHLSL) KEYWORD(groupshared , KEYHLSL) +KEYWORD(nointerpolation , KEYHLSL) +KEYWORD(linear , KEYHLSL) +KEYWORD(centroid , KEYHLSL) +KEYWORD(noperspective , KEYHLSL) +KEYWORD(sample , KEYHLSL) +KEYWORD(center , KEYHLSL) KEYWORD(in , KEYHLSL) KEYWORD(inout , KEYHLSL) KEYWORD(out , KEYHLSL) diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 5976f5a7ccdea..6fb777a9416bc 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1094,7 +1094,9 @@ void Parser::ParseOpenCLQualifiers(ParsedAttributes &Attrs) { bool Parser::isHLSLQualifier(const Token &Tok) const { return Tok.is(tok::kw_groupshared) || Tok.is(tok::kw_row_major) || - Tok.is(tok::kw_column_major); + Tok.is(tok::kw_column_major) || + Tok.isOneOf(tok::kw_nointerpolation, tok::kw_linear, tok::kw_centroid, + tok::kw_noperspective, tok::kw_sample, tok::kw_center); } void Parser::ParseHLSLQualifiers(ParsedAttributes &Attrs) { @@ -4657,6 +4659,12 @@ void Parser::ParseDeclarationSpecifiers( break; case tok::kw_row_major: case tok::kw_column_major: + case tok::kw_nointerpolation: + case tok::kw_linear: + case tok::kw_centroid: + case tok::kw_noperspective: + case tok::kw_sample: + case tok::kw_center: case tok::kw_groupshared: case tok::kw_in: case tok::kw_inout: @@ -5771,6 +5779,12 @@ bool Parser::isTypeSpecifierQualifier(const Token &Tok) { return true; // HLSL type qualifiers + case tok::kw_nointerpolation: + case tok::kw_linear: + case tok::kw_centroid: + case tok::kw_noperspective: + case tok::kw_sample: + case tok::kw_center: case tok::kw_groupshared: case tok::kw_in: case tok::kw_inout: @@ -6055,6 +6069,12 @@ bool Parser::isDeclarationSpecifier( case tok::kw_groupshared: return true; + case tok::kw_nointerpolation: + case tok::kw_linear: + case tok::kw_centroid: + case tok::kw_noperspective: + case tok::kw_sample: + case tok::kw_center: case tok::kw_row_major: case tok::kw_column_major: return getLangOpts().HLSL; @@ -6301,6 +6321,12 @@ void Parser::ParseTypeQualifierListOpt( case tok::kw_in: case tok::kw_inout: case tok::kw_out: + case tok::kw_nointerpolation: + case tok::kw_linear: + case tok::kw_centroid: + case tok::kw_noperspective: + case tok::kw_sample: + case tok::kw_center: // NOTE: ParseHLSLQualifiers will consume the qualifier token. ParseHLSLQualifiers(DS.getAttributes()); continue; diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp index 6491baf0af92c..989801b0cfa84 100644 --- a/clang/lib/Parse/ParseTentative.cpp +++ b/clang/lib/Parse/ParseTentative.cpp @@ -1286,7 +1286,13 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename, // OpenCL pipe case tok::kw_pipe: - // HLSL address space qualifiers + // HLSL interpolation and address space qualifiers + case tok::kw_nointerpolation: + case tok::kw_linear: + case tok::kw_centroid: + case tok::kw_noperspective: + case tok::kw_sample: + case tok::kw_center: case tok::kw_groupshared: case tok::kw_in: case tok::kw_inout: diff --git a/clang/test/ParserHLSL/interpolation-keywords.hlsl b/clang/test/ParserHLSL/interpolation-keywords.hlsl new file mode 100644 index 0000000000000..462c021ce986d --- /dev/null +++ b/clang/test/ParserHLSL/interpolation-keywords.hlsl @@ -0,0 +1,6 @@ +// RUN: %clang_cc1 -x c++ -verify %s +// RUN: %clang_cc1 -x c -verify %s +// expected-no-diagnostics + +// Interpolation modifiers are keywords only in HLSL. +int nointerpolation, linear, centroid, noperspective, sample, center; diff --git a/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl index b22dae4d1bae9..02fcad2801121 100644 --- a/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl +++ b/clang/test/SemaHLSL/BuiltIns/resource_load-errors.hlsl @@ -31,9 +31,9 @@ export void load(tex_t t, int3 loc, vector<int64_t,3> wide_loc, uint3 uloc, __builtin_hlsl_resource_load_level(t, loc, foff); } -export void load_ms(tex_ms_t t, int2 loc, int sample, int64_t lsample, +export void load_ms(tex_ms_t t, int2 loc, int sample_index, int64_t lsample, float fsample, int2 off, uint2 uoff, float2 foff) { - __builtin_hlsl_resource_load_ms(t, loc, sample); + __builtin_hlsl_resource_load_ms(t, loc, sample_index); __builtin_hlsl_resource_load_ms(t, loc, 0, off); // expected-error@+1 {{passing 'int64_t' (aka 'long') to parameter of incompatible type 'int'}} >From e90131695887981fcd703fb784d334afaae6e243 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Fri, 18 Sep 2026 17:22:25 +0000 Subject: [PATCH 02/10] attach and validate interpolation modifier attributes --- clang/include/clang/Basic/Attr.td | 10 +- clang/include/clang/Basic/AttrDocs.td | 18 ++ .../clang/Basic/DiagnosticSemaKinds.td | 12 ++ clang/include/clang/Sema/SemaHLSL.h | 6 + clang/lib/Sema/SemaDeclAttr.cpp | 3 + clang/lib/Sema/SemaHLSL.cpp | 149 ++++++++++++-- .../interpolation-modifier-warnings.hlsl | 12 ++ .../SemaHLSL/interpolation-modifiers-ast.hlsl | 61 ++++++ .../SemaHLSL/interpolation-modifiers.hlsl | 185 ++++++++++++++++++ .../llvm/Frontend/HLSL/SemanticSignatures.h | 31 +++ llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 35 ++++ llvm/unittests/Frontend/CMakeLists.txt | 1 + .../Frontend/HLSLInterpolationTest.cpp | 72 +++++++ 13 files changed, 582 insertions(+), 13 deletions(-) create mode 100644 clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl create mode 100644 clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl create mode 100644 clang/test/SemaHLSL/interpolation-modifiers.hlsl create mode 100644 llvm/unittests/Frontend/HLSLInterpolationTest.cpp diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 5e7dd712b0aae..5491491d60011 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -5321,12 +5321,18 @@ def HLSLGroupSharedAddressSpace : TypeAttr { let Documentation = [HLSLGroupSharedAddressSpaceDocs]; } -// Parse interpolation modifiers without attaching them to declarations yet. -def HLSLInterpolationModifier : IgnoredAttr { +def HLSLInterpolationModifiersArgument : UnsignedArgument<"Modifiers"> { + let Fake = 1; +} + +def HLSLInterpolationModifier : InheritableAttr { let Spellings = [CustomKeyword<"nointerpolation">, CustomKeyword<"linear">, CustomKeyword<"centroid">, CustomKeyword<"noperspective">, CustomKeyword<"sample">, CustomKeyword<"center">]; + let Args = [HLSLInterpolationModifiersArgument]; + let Subjects = SubjectList<[ParmVar, Field, Function], ErrorDiag>; let LangOpts = [HLSL]; + let Documentation = [HLSLInterpolationModifierDocs]; } def HLSLParamModifier : ParameterABIAttr { diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 6170688ae8cf9..5fe9d76ba967d 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -9384,6 +9384,24 @@ The full documentation is available here: <https://learn.microsoft.com/en-us/win }]; } +def HLSLInterpolationModifierDocs : Documentation { + let Category = DocCatVariable; + let Heading = "HLSL Interpolation Modifiers"; + let Content = [{ +The HLSL keywords `nointerpolation`, `linear`, `centroid`, +`noperspective`, `sample`, and `center` control interpolation of pixel +shader inputs. + +When applied to an aggregate type, the modifier propagates recursively to every +scalar or vector of the type. A modifier on an inner field overrides one +inherited from an enclosing declaration. + +`nointerpolation` cannot be combined with another interpolation modifier and +cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point +input components only support `nointerpolation`. + }]; +} + def HLSLParamQualifierDocs : Documentation { let Category = DocCatVariable; let Heading = "HLSL Parameter Modifiers"; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 36a18473f4d4c..c84e9a18cadd6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13706,6 +13706,18 @@ def err_hlsl_numthreads_invalid : Error<"total number of threads cannot exceed % def err_hlsl_missing_numthreads : Error<"missing numthreads attribute for %0 shader entry">; def err_hlsl_attribute_param_mismatch : Error<"%0 attribute parameters do not match the previous declaration">; def err_hlsl_duplicate_parameter_modifier : Error<"duplicate parameter modifier %0">; +def err_hlsl_interpolation_conflict : Error< + "'nointerpolation' cannot be combined with other interpolation modifiers">; +def err_hlsl_interpolation_position : Error< + "'nointerpolation' cannot be used on SV_Position">; +def err_hlsl_interpolation_type : Error< + "interpolation modifiers other than 'nointerpolation' cannot be used with type %0">; +def warn_hlsl_duplicate_interpolation : Warning< + "duplicate interpolation modifier %0">, + InGroup<DuplicateDeclSpecifier>; +def warn_hlsl_interpolation_override : Warning< + "interpolation modifier '%0' overrides '%1'">, + InGroup<IgnoredAttributes>; def err_hlsl_missing_semantic_annotation : Error< "semantic annotations must be present for all parameters of an entry " "function or patch constant function">; diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 6c0e5b52f7cb3..764635e40ce5a 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -191,6 +191,7 @@ class SemaHLSL : public SemaBase { void handleShaderAttr(Decl *D, const ParsedAttr &AL); void handleResourceBindingAttr(Decl *D, const ParsedAttr &AL); void handleParamModifierAttr(Decl *D, const ParsedAttr &AL); + void handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL); Attr *buildMatrixLayoutTypeAttr(QualType T, const ParsedAttr &AL); bool diagnoseMatrixLayoutInstantiation(attr::Kind K, QualType T, SourceLocation Loc); @@ -308,6 +309,11 @@ class SemaHLSL : public SemaBase { }; private: + bool + checkInterpolationModifiers(const DeclaratorDecl *D, + const HLSLInterpolationModifierAttr *Inherited, + const HLSLParsedSemanticAttr *Semantic); + void collectResourceBindingsOnVarDecl(VarDecl *D); void collectResourceBindingsOnUserRecordDecl(const VarDecl *VD, const RecordType *RT); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index eb4a8c2ab9ae0..da7472c1cba1b 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8272,6 +8272,9 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_HLSLResourceBinding: S.HLSL().handleResourceBindingAttr(D, AL); break; + case ParsedAttr::AT_HLSLInterpolationModifier: + S.HLSL().handleInterpolationModifierAttr(D, AL); + break; case ParsedAttr::AT_HLSLParamModifier: S.HLSL().handleParamModifierAttr(D, AL); break; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 4a64e9ea8a1dd..17664ca7451df 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -47,6 +47,7 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/TargetParser/Triple.h" +#include <algorithm> #include <cmath> #include <cstddef> #include <iterator> @@ -54,6 +55,7 @@ using namespace clang; using namespace clang::hlsl; +using llvm::hlsl::InterpolationModifier; using llvm::hlsl::IOType; using llvm::hlsl::SemanticStageInfo; using SemanticKind = llvm::dxbc::PSV::SemanticKind; @@ -62,6 +64,14 @@ using RegisterType = HLSLResourceBindingAttr::RegisterType; static CXXRecordDecl *createHostLayoutStruct(Sema &S, CXXRecordDecl *StructDecl); +static QualType getScalarComponentType(QualType T) { + if (const auto *VT = T->getAs<VectorType>()) + return VT->getElementType(); + if (const auto *MT = T->getAs<MatrixType>()) + return MT->getElementType(); + return T; +} + static RegisterType getRegisterType(ResourceClass RC) { switch (RC) { case ResourceClass::SRV: @@ -817,6 +827,126 @@ SemaHLSL::mergeParamModifierAttr(Decl *D, const AttributeCommonInfo &AL, return HLSLParamModifierAttr::Create(getASTContext(), AL); } +static StringRef +getInterpolationSamplingLocationName(InterpolationModifier Location) { + switch (Location) { + case InterpolationModifier::Sample: + return "sample"; + case InterpolationModifier::Centroid: + return "centroid"; + case InterpolationModifier::Center: + return "center"; + default: + llvm_unreachable("expected an explicit interpolation sampling location"); + } +} + +void SemaHLSL::handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL) { + InterpolationModifier Modifier; + switch (static_cast<HLSLInterpolationModifierAttr::Spelling>( + AL.getSemanticSpelling())) { + case HLSLInterpolationModifierAttr::Keyword_nointerpolation: + Modifier = InterpolationModifier::NoInterpolation; + break; + case HLSLInterpolationModifierAttr::Keyword_linear: + Modifier = InterpolationModifier::Linear; + break; + case HLSLInterpolationModifierAttr::Keyword_centroid: + Modifier = InterpolationModifier::Centroid; + break; + case HLSLInterpolationModifierAttr::Keyword_noperspective: + Modifier = InterpolationModifier::NoPerspective; + break; + case HLSLInterpolationModifierAttr::Keyword_sample: + Modifier = InterpolationModifier::Sample; + break; + case HLSLInterpolationModifierAttr::Keyword_center: + Modifier = InterpolationModifier::Center; + break; + case HLSLInterpolationModifierAttr::SpellingNotCalculated: + llvm_unreachable("interpolation modifier spelling was not calculated"); + } + + InterpolationModifier Modifiers = Modifier; + if (auto *Previous = D->getAttr<HLSLInterpolationModifierAttr>()) { + auto Old = static_cast<InterpolationModifier>(Previous->getModifiers()); + Modifiers |= Old; + if (any(Old & Modifier)) { + Diag(AL.getLoc(), diag::warn_hlsl_duplicate_interpolation) << AL; + } else if (llvm::hlsl::getInterpolationMode(Modifiers) == + llvm::dxbc::PSV::InterpolationMode::Invalid && + llvm::hlsl::getInterpolationMode(Old) != + llvm::dxbc::PSV::InterpolationMode::Invalid) { + Diag(AL.getLoc(), diag::err_hlsl_interpolation_conflict); + Diag(Previous->getLocation(), diag::note_conflicting_attribute); + D->setInvalidDecl(); + } else { + InterpolationModifier OldLocation = + llvm::hlsl::getInterpolationSamplingLocation(Old); + InterpolationModifier NewLocation = + llvm::hlsl::getInterpolationSamplingLocation(Modifier); + if (any(OldLocation) && any(NewLocation)) { + Diag(AL.getLoc(), diag::warn_hlsl_interpolation_override) + << getInterpolationSamplingLocationName( + std::max(OldLocation, NewLocation)) + << getInterpolationSamplingLocationName( + std::min(OldLocation, NewLocation)); + } + } + D->dropAttr<HLSLInterpolationModifierAttr>(); + } + D->addAttr(HLSLInterpolationModifierAttr::Create( + getASTContext(), static_cast<unsigned>(Modifiers), AL)); +} + +bool SemaHLSL::checkInterpolationModifiers( + const DeclaratorDecl *D, const HLSLInterpolationModifierAttr *Inherited, + const HLSLParsedSemanticAttr *Semantic) { + if (D->isInvalidDecl()) + return false; + const auto *A = D->getAttr<HLSLInterpolationModifierAttr>(); + if (!A) + A = Inherited; + if (!Semantic) + Semantic = D->getAttr<HLSLParsedSemanticAttr>(); + + QualType T = + getASTContext().getBaseElementType(D->getType().getNonReferenceType()); + if (T->isDependentType()) + return true; + if (const auto *RT = T->getAs<RecordType>()) { + const RecordDecl *RD = RT->getDecl()->getDefinition(); + if (!RD) + return true; + bool Valid = true; + for (const FieldDecl *Field : RD->fields()) + Valid &= checkInterpolationModifiers(Field, A, Semantic); + return Valid; + } + if (!A) + return true; + + auto Modifiers = static_cast<InterpolationModifier>(A->getModifiers()); + if (Modifiers == InterpolationModifier::NoInterpolation) { + bool IsPosition = + Semantic && llvm::hlsl::getSemanticKind(Semantic->getSemanticName()) == + SemanticKind::Position; + if (!IsPosition) + return true; + Diag(A->getLocation(), diag::err_hlsl_interpolation_position); + Diag(Semantic->getLocation(), diag::note_conflicting_attribute); + return false; + } + + T = getScalarComponentType(T); + if (T->isIntegerType() || + (T->isRealFloatingType() && getASTContext().getTypeSize(T) > 32)) { + Diag(A->getLocation(), diag::err_hlsl_interpolation_type) << T; + return false; + } + return true; +} + void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) { auto &TargetInfo = getASTContext().getTargetInfo(); @@ -1067,6 +1197,12 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) { const auto *MA = Param->getAttr<HLSLParamModifierAttr>(); SemanticContext &SC = MA && MA->isAnyOut() ? OutputSC : InputSC; + // Interpolation only applies to pixel shader inputs, including the input + // side of inout parameters. Other stages and output signatures ignore it. + if (ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn()) && + !checkInterpolationModifiers(Param, nullptr, nullptr)) + FD->setInvalidDecl(); + if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic, SC)) { Diag(Param->getLocation(), diag::note_previous_decl) << Param; FD->setInvalidDecl(); @@ -3346,11 +3482,7 @@ static bool CheckFloatRepresentation(Sema *S, SourceLocation Loc, static bool CheckFloatOrHalfRepresentation(Sema *S, SourceLocation Loc, int ArgOrdinal, clang::QualType PassedType) { - clang::QualType BaseType = PassedType; - if (const auto *VT = PassedType->getAs<clang::VectorType>()) - BaseType = VT->getElementType(); - else if (const auto *MT = PassedType->getAs<clang::MatrixType>()) - BaseType = MT->getElementType(); + QualType BaseType = getScalarComponentType(PassedType); if (!BaseType->isHalfType() && !BaseType->isFloat32Type()) return S->Diag(Loc, diag::err_builtin_invalid_arg_type) @@ -3362,12 +3494,7 @@ static bool CheckFloatOrHalfRepresentation(Sema *S, SourceLocation Loc, static bool CheckAnyDoubleRepresentation(Sema *S, SourceLocation Loc, int ArgOrdinal, clang::QualType PassedType) { - clang::QualType BaseType = - PassedType->isVectorType() - ? PassedType->castAs<clang::VectorType>()->getElementType() - : PassedType->isMatrixType() - ? PassedType->castAs<clang::MatrixType>()->getElementType() - : PassedType; + QualType BaseType = getScalarComponentType(PassedType); if (!BaseType->isDoubleType()) { // FIXME: adopt standard `err_builtin_invalid_arg_type` instead of using // this custom error. diff --git a/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl b/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl new file mode 100644 index 0000000000000..95791316f7469 --- /dev/null +++ b/clang/test/SemaHLSL/interpolation-modifier-warnings.hlsl @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -verify %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -Wno-duplicate-decl-specifier -verify=override %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -Wno-ignored-attributes -verify=duplicate %s + +// The existing diagnostic groups independently control duplicates and overrides. +// expected-warning@+2 {{duplicate interpolation modifier 'linear'}} +// duplicate-warning@+1 {{duplicate interpolation modifier 'linear'}} +void duplicates(linear linear float x); + +// expected-warning@+2 {{interpolation modifier 'sample' overrides 'center'}} +// override-warning@+1 {{interpolation modifier 'sample' overrides 'center'}} +void locations(center sample float x); diff --git a/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl b/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl new file mode 100644 index 0000000000000..be892d68d6fef --- /dev/null +++ b/clang/test/SemaHLSL/interpolation-modifiers-ast.hlsl @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -Wno-duplicate-decl-specifier -Wno-ignored-attributes -ast-dump %s | FileCheck %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -Wno-duplicate-decl-specifier -Wno-ignored-attributes -emit-pch -o %t %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -include-pch %t -ast-dump-all -x hlsl /dev/null | FileCheck %s + +void modifiers(nointerpolation float a, linear float b, centroid float c, + noperspective float d, sample float e, center float f); +// CHECK-LABEL: FunctionDecl {{.*}} modifiers +// CHECK: ParmVarDecl {{.*}} a 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 1 +// CHECK: ParmVarDecl {{.*}} b 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2 +// CHECK: ParmVarDecl {{.*}} c 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 16 +// CHECK: ParmVarDecl {{.*}} d 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 4 +// CHECK: ParmVarDecl {{.*}} e 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32 +// CHECK: ParmVarDecl {{.*}} f 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 8 + +void merged(linear sample noperspective float a, + noperspective sample linear float b, + center centroid sample linear float c, + linear linear float d); +// CHECK-LABEL: FunctionDecl {{.*}} merged +// CHECK: ParmVarDecl {{.*}} a 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 38 +// CHECK-NOT: HLSLInterpolationModifierAttr +// CHECK: ParmVarDecl {{.*}} b 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 38 +// CHECK-NOT: HLSLInterpolationModifierAttr +// CHECK: ParmVarDecl {{.*}} c 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 58 +// CHECK-NOT: HLSLInterpolationModifierAttr +// CHECK: ParmVarDecl {{.*}} d 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2 +// CHECK-NOT: HLSLInterpolationModifierAttr + +struct Fields { + centroid float x; + float noperspective y; +}; +// CHECK-LABEL: CXXRecordDecl {{.*}} struct Fields definition +// CHECK: FieldDecl {{.*}} x 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 16 +// CHECK: FieldDecl {{.*}} y 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 4 + +linear float result(); +// CHECK-LABEL: FunctionDecl {{.*}} result +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 2 + +// No entry point is needed to preserve modifiers on a dependent declaration. +template<typename T> void dependent(sample T x) {} +void instantiate() { dependent(1.0f); } +// CHECK-LABEL: FunctionTemplateDecl {{.*}} dependent +// CHECK: ParmVarDecl {{.*}} x 'T' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32 +// CHECK: FunctionDecl {{.*}} dependent 'void (float)' implicit_instantiation +// CHECK: ParmVarDecl {{.*}} x 'float' +// CHECK-NEXT: HLSLInterpolationModifierAttr {{.*}} 32 diff --git a/clang/test/SemaHLSL/interpolation-modifiers.hlsl b/clang/test/SemaHLSL/interpolation-modifiers.hlsl new file mode 100644 index 0000000000000..b278eba8d26a7 --- /dev/null +++ b/clang/test/SemaHLSL/interpolation-modifiers.hlsl @@ -0,0 +1,185 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -verify %s + +// Each keyword is incompatible with nointerpolation, in either order. +void conflicts( + // expected-error@+2 {{'nointerpolation' cannot be combined}} + // expected-note@+1 {{conflicting attribute is here}} + nointerpolation linear float a, + // expected-error@+2 {{'nointerpolation' cannot be combined}} + // expected-note@+1 {{conflicting attribute is here}} + centroid nointerpolation float b, + // expected-error@+2 {{'nointerpolation' cannot be combined}} + // expected-note@+1 {{conflicting attribute is here}} + nointerpolation noperspective float c, + // expected-error@+2 {{'nointerpolation' cannot be combined}} + // expected-note@+1 {{conflicting attribute is here}} + sample nointerpolation float d, + // expected-error@+2 {{'nointerpolation' cannot be combined}} + // expected-note@+1 {{conflicting attribute is here}} + nointerpolation center float e); + +void duplicates( + // expected-warning@+1 {{duplicate interpolation modifier 'nointerpolation'}} + nointerpolation nointerpolation float a, + // expected-warning@+1 {{duplicate interpolation modifier 'linear'}} + linear linear float b, + // expected-warning@+1 {{duplicate interpolation modifier 'centroid'}} + centroid centroid float c, + // expected-warning@+1 {{duplicate interpolation modifier 'noperspective'}} + noperspective noperspective float d, + // expected-warning@+1 {{duplicate interpolation modifier 'sample'}} + sample sample float e, + // expected-warning@+1 {{duplicate interpolation modifier 'center'}} + center center float f); + +void locations( + // expected-warning@+1 {{interpolation modifier 'centroid' overrides 'center'}} + center centroid float a, + // expected-warning@+1 {{interpolation modifier 'centroid' overrides 'center'}} + centroid center float b, + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'center'}} + center sample float c, + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'center'}} + sample center float d, + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + centroid sample float e, + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + sample centroid float f); + +// With all three locations, compare the new keyword against the previous +// highest-precedence location, regardless of keyword order. +void all_locations( + // expected-warning@+2 {{interpolation modifier 'centroid' overrides 'center'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + center centroid sample float a, + // expected-warning@+2 {{interpolation modifier 'sample' overrides 'center'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + center sample centroid float b, + // expected-warning@+2 {{interpolation modifier 'centroid' overrides 'center'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + centroid center sample float c, + // expected-warning@+2 {{interpolation modifier 'sample' overrides 'centroid'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'center'}} + centroid sample center float d, + // expected-warning@+2 {{interpolation modifier 'sample' overrides 'center'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'centroid'}} + sample center centroid float e, + // expected-warning@+2 {{interpolation modifier 'sample' overrides 'centroid'}} + // expected-warning@+1 {{interpolation modifier 'sample' overrides 'center'}} + sample centroid center float f); + +// Interpolation modes do not imply an explicit sampling location, so combining +// them with a location must not produce an override warning in either order. +void implicit_locations(linear centroid float a, centroid linear float b, + noperspective sample float c, sample noperspective float d, + linear center float e, center linear float f); + +// Type restrictions apply to pixel shader inputs, not arbitrary declarations. +[shader("pixel")] +float4 invalid_types( + // expected-error@+1 {{cannot be used with type 'int'}} + linear int a : A, + // expected-error@+1 {{cannot be used with type 'uint'}} + centroid uint2 b : B, + // expected-error@+1 {{cannot be used with type 'bool'}} + noperspective bool c : C, + // expected-error@+1 {{cannot be used with type 'double'}} + sample double d : D, + // expected-error@+1 {{cannot be used with type 'int'}} + center int e[2][3] : E, + // expected-error@+1 {{cannot be used with type 'double'}} + linear double2x2 f : F, + // expected-error@+1 {{cannot be used with type 'int'}} + linear in int g : G) : SV_Target { + return 0; +} + +[shader("pixel")] +float4 valid_types(nointerpolation int a : A, nointerpolation bool2 b : B, + nointerpolation double c : C, nointerpolation int d[2] : D, + nointerpolation double2x2 e : E, linear float f : F, + centroid half2 g : G, sample float2x2 h : H) : SV_Target { + return 0; +} + +// The input side of an inout parameter also needs interpolation validation. +// SV_Target currently rejects integer types independently of interpolation. +[shader("pixel")] +// expected-error@+2 {{cannot be used with type 'uint'}} +// expected-error@+1 {{attribute 'SV_Target' only applies to a field or parameter of type}} +void invalid_inout(linear inout uint a : SV_Target) { + a = 0; +} + +struct Position { + // expected-note@+1 {{conflicting attribute is here}} + float4 p : SV_Position; +}; +[shader("pixel")] +// expected-error@+1 {{'nointerpolation' cannot be used on SV_Position}} +float4 inherited_position(nointerpolation Position p) : SV_Target { + return 0; +} +[shader("pixel")] +// expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}} +// expected-note@+1 {{conflicting attribute is here}} +float4 direct_position(nointerpolation float4 p : SV_Position) : SV_Target { + return 0; +} + +struct Inner { + int value; +}; +struct Outer { + Inner inner; +}; +[shader("pixel")] +// expected-error@+1 {{cannot be used with type 'int'}} +float4 invalid_aggregate(linear Outer x : A) : SV_Target { + return 0; +} + +// Field modifiers are checked even without an enclosing modifier, including +// through unannotated aggregates. The field's declaration is not itself invalid. +struct FieldModifiers { + // expected-error@+1 {{cannot be used with type 'uint'}} + sample uint value; +}; +struct FieldOuter { + FieldModifiers inner; +}; +[shader("pixel")] +float4 invalid_field_modifier(FieldOuter x : A) : SV_Target { return 0; } +[shader("vertex")] +float4 valid_field_modifier(FieldOuter x : A) : SV_Position { return 0; } + +struct FlatPosition { + // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}} + // expected-note@+1 {{conflicting attribute is here}} + nointerpolation float4 p : SV_Position; +}; +[shader("pixel")] +float4 invalid_position_field(FlatPosition p) : SV_Target { return 0; } + +// An inner modifier replaces the entire inherited set, not just one keyword. +struct Overrides { + nointerpolation int i; + linear float f; +}; +[shader("pixel")] +float4 valid_aggregate(sample Overrides x : A) : SV_Target { return 0; } +struct PositionOverride { + linear float4 p : SV_Position; +}; +[shader("pixel")] +float4 valid_position(nointerpolation PositionOverride p) : SV_Target { return 0; } + +// Subject restrictions also apply outside entry points. +// expected-error@+1 {{'linear' attribute only applies to parameters, non-static data members, and functions}} +linear float global; +// expected-error@+1 {{'sample' attribute only applies to parameters, non-static data members, and functions}} +typedef sample float Alias; +void local() { + // expected-error@+1 {{'centroid' attribute only applies to parameters, non-static data members, and functions}} + centroid float value; +} diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h index 975fc2032d46e..ceb40c806c958 100644 --- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h +++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h @@ -49,6 +49,37 @@ enum class IOType { LLVM_MARK_AS_BITMASK_ENUM(PatchConstantOrPrimitive), }; +// Keywords written on a declaration, before sampling-location precedence and +// stage/type defaults are applied. +enum class InterpolationModifier : uint32_t { + None = 0, + NoInterpolation = 1 << 0, + Linear = 1 << 1, + NoPerspective = 1 << 2, + // Sampling-location bits are ordered by increasing precedence for min/max. + Center = 1 << 3, + Centroid = 1 << 4, + Sample = 1 << 5, + LLVM_MARK_AS_BITMASK_ENUM(Sample), +}; + +static_assert(InterpolationModifier::None < InterpolationModifier::Center && + InterpolationModifier::Center < + InterpolationModifier::Centroid && + InterpolationModifier::Centroid < + InterpolationModifier::Sample, + "sampling-location bits must increase in precedence"); + +// Select the highest-precedence sampling-location bit (sample > centroid > +// center), ignoring other modifiers. Returns None if no location is explicit, +// keeping it distinct from Center so diagnostics can detect location overrides. +LLVM_ABI InterpolationModifier +getInterpolationSamplingLocation(InterpolationModifier Modifiers); + +// Returns Invalid for nointerpolation combined with any other modifier. +LLVM_ABI dxbc::PSV::InterpolationMode +getInterpolationMode(InterpolationModifier Modifiers); + enum class SemanticInterpretation { Invalid, NotAllocated, diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp index efc84447a45f9..4bdef5bd02ffa 100644 --- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp +++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp @@ -19,10 +19,12 @@ #include "llvm/IR/Metadata.h" #include "llvm/IR/Type.h" #include "llvm/Support/ErrorHandling.h" +#include <algorithm> #include <cassert> using namespace llvm; using namespace llvm::hlsl; +using llvm::dxbc::PSV::InterpolationMode; namespace { @@ -46,6 +48,39 @@ Expected<uint64_t> extractInt(const MDNode *Node, unsigned OpId) { } } // namespace +InterpolationModifier +hlsl::getInterpolationSamplingLocation(InterpolationModifier Modifiers) { + return std::max({Modifiers & InterpolationModifier::Center, + Modifiers & InterpolationModifier::Centroid, + Modifiers & InterpolationModifier::Sample}); +} + +dxbc::PSV::InterpolationMode +hlsl::getInterpolationMode(InterpolationModifier Modifiers) { + if (Modifiers == InterpolationModifier::None) + return InterpolationMode::Undefined; + if (any(Modifiers & InterpolationModifier::NoInterpolation)) + return Modifiers == InterpolationModifier::NoInterpolation + ? InterpolationMode::Constant + : InterpolationMode::Invalid; + + bool NoPerspective = any(Modifiers & InterpolationModifier::NoPerspective); + switch (getInterpolationSamplingLocation(Modifiers)) { + case InterpolationModifier::Sample: + return NoPerspective ? InterpolationMode::LinearNoperspectiveSample + : InterpolationMode::LinearSample; + case InterpolationModifier::Centroid: + return NoPerspective ? InterpolationMode::LinearNoperspectiveCentroid + : InterpolationMode::LinearCentroid; + case InterpolationModifier::Center: + case InterpolationModifier::None: + return NoPerspective ? InterpolationMode::LinearNoperspective + : InterpolationMode::Linear; + default: + llvm_unreachable("invalid interpolation sampling location"); + } +} + dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) { if (!SemanticName.consume_front_insensitive("SV_")) return dxbc::PSV::SemanticKind::Arbitrary; diff --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt index 2373ae29e0599..2f0052b5eb601 100644 --- a/llvm/unittests/Frontend/CMakeLists.txt +++ b/llvm/unittests/Frontend/CMakeLists.txt @@ -15,6 +15,7 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(LLVMFrontendTests EnumSetTest.cpp HLSLBindingTest.cpp + HLSLInterpolationTest.cpp HLSLRootSignatureDumpTest.cpp HLSLSemanticStagesTest.cpp HLSLSemanticSignatureMetadataTest.cpp diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp new file mode 100644 index 0000000000000..354475c5a7e5f --- /dev/null +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -0,0 +1,72 @@ +//===- HLSLInterpolationTest.cpp ------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Frontend/HLSL/SemanticSignatures.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::hlsl; +using Mod = InterpolationModifier; +using InterpMode = dxbc::PSV::InterpolationMode; + +TEST(HLSLInterpolationTest, AllModifierSets) { + // Cover every subset, including redundant linear/center modifiers and all + // combinations of competing sampling locations. + for (unsigned Mask = 0; Mask != 64; ++Mask) { + SCOPED_TRACE(Mask); + auto Modifiers = static_cast<Mod>(Mask); + InterpMode Expected; + if (Modifiers == Mod::None) + Expected = InterpMode::Undefined; + else if (Modifiers == Mod::NoInterpolation) + Expected = InterpMode::Constant; + else if (any(Modifiers & Mod::NoInterpolation)) + Expected = InterpMode::Invalid; + else { + static constexpr InterpMode Modes[2][3] = { + {InterpMode::Linear, InterpMode::LinearCentroid, + InterpMode::LinearSample}, + {InterpMode::LinearNoperspective, + InterpMode::LinearNoperspectiveCentroid, + InterpMode::LinearNoperspectiveSample}}; + unsigned Location = any(Modifiers & Mod::Sample) ? 2 + : any(Modifiers & Mod::Centroid) ? 1 + : 0; + Expected = Modes[any(Modifiers & Mod::NoPerspective)][Location]; + } + EXPECT_EQ(getInterpolationMode(Modifiers), Expected); + } +} + +TEST(HLSLInterpolationTest, SamplingLocations) { + const struct { + Mod Modifiers; + Mod Expected; + } Cases[] = { + {Mod::None, Mod::None}, + {Mod::Center, Mod::Center}, + {Mod::Centroid, Mod::Centroid}, + {Mod::Center | Mod::Centroid, Mod::Centroid}, + {Mod::Sample, Mod::Sample}, + {Mod::Center | Mod::Sample, Mod::Sample}, + {Mod::Centroid | Mod::Sample, Mod::Sample}, + {Mod::Center | Mod::Centroid | Mod::Sample, Mod::Sample}, + }; + // Non-location keywords must not imply an explicit center modifier or change + // sampling-location precedence, even in invalid interpolation combinations. + for (const auto &Case : Cases) + for (Mod Other : + {Mod::None, Mod::NoInterpolation, Mod::Linear, Mod::NoPerspective, + Mod::Linear | Mod::NoPerspective, Mod::NoInterpolation | Mod::Linear, + Mod::NoInterpolation | Mod::NoPerspective, + Mod::NoInterpolation | Mod::Linear | Mod::NoPerspective}) { + Mod Modifiers = Case.Modifiers | Other; + SCOPED_TRACE(static_cast<unsigned>(Modifiers)); + EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected); + } +} >From 6584f4d6fea5d13d19f143ce261e24b76784fe3b Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Fri, 18 Sep 2026 17:22:31 +0000 Subject: [PATCH 03/10] lower interpolation modifiers to semantic signature metadata --- clang/include/clang/Basic/AttrDocs.td | 5 + clang/lib/CodeGen/CGHLSLRuntime.cpp | 34 +++++-- clang/lib/CodeGen/CGHLSLRuntime.h | 7 +- .../semantics/interpolation-aggregate.hlsl | 45 +++++++++ .../semantics/interpolation-no-entry.hlsl | 0 .../CodeGenHLSL/semantics/interpolation.hlsl | 99 +++++++++++++++++++ .../llvm/Frontend/HLSL/SemanticSignatures.h | 7 ++ llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 37 +++++++ .../Frontend/HLSLInterpolationTest.cpp | 55 +++++++++++ 9 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation.hlsl diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 5fe9d76ba967d..fa8e2c0502f66 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -9399,6 +9399,11 @@ inherited from an enclosing declaration. `nointerpolation` cannot be combined with another interpolation modifier and cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point input components only support `nointerpolation`. + +Unqualified pixel shader inputs default to `linear` for floating-point +components of at most 32 bits and `nointerpolation` otherwise. `SV_Position` +uses the corresponding `noperspective` mode. Other shader stages and output +signatures have no interpolation mode and are ignored. }]; } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index 52fde18dc3ebd..a15b3490b8827 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1299,9 +1299,8 @@ static llvm::hlsl::SemanticSignatureElement createSemanticSignatureElement( SemanticIndices.push_back(FirstSemanticIndex + I); // The remaining members keep their default value and will be filled at a - // later stage, either during packing or analysis of usage - // - // FIXME #189762: Element.InterpMode is to be set + // later stage, either during packing or analysis of usage. Input + // interpolation is set by handleSemanticLoad after visiting each leaf. return llvm::hlsl::SemanticSignatureElement( SigId, Name, getSignatureComponentType(CGM, Shape.RowType), llvm::hlsl::getSemanticKind(Name), SemanticIndices, @@ -1663,7 +1662,8 @@ CGHLSLRuntime::handleStructSemanticLoad( const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature) { + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers) { const llvm::StructType *ST = cast<StructType>(Type); const clang::RecordDecl *RD = Decl->getType()->getAsRecordDecl(); @@ -1674,7 +1674,7 @@ CGHLSLRuntime::handleStructSemanticLoad( for (unsigned I = 0; I < ST->getNumElements(); ++I) { auto [ChildValue, NextAttr] = handleSemanticLoad(B, FD, ST->getElementType(I), *FieldDecl, AttrBegin, - AttrEnd, Signature); + AttrEnd, Signature, Modifiers); AttrBegin = NextAttr; assert(ChildValue); Aggregate = B.CreateInsertValue(Aggregate, ChildValue, I); @@ -1719,16 +1719,32 @@ CGHLSLRuntime::handleSemanticLoad( const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature) { + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers) { assert(AttrBegin != AttrEnd); + // Pass the enclosing declaration's mask down the traversal, replacing (not + // merging) it when an inner field has its own interpolation modifiers. + if (const auto *A = Decl->getAttr<HLSLInterpolationModifierAttr>()) + Modifiers = + static_cast<llvm::hlsl::InterpolationModifier>(A->getModifiers()); if (Type->isStructTy()) return handleStructSemanticLoad(B, FD, Type, Decl, AttrBegin, AttrEnd, - Signature); + Signature, Modifiers); HLSLAppliedSemanticAttr *Attr = *AttrBegin; ++AttrBegin; - return std::make_pair( - handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature), AttrBegin); + size_t PreviousSize = Signature.size(); + llvm::Value *Value = + handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature); + // Intrinsic-only system values and SPIR-V loads do not add DXIL signature + // elements. Outputs are not interpolated and retain Undefined. + if (Signature.size() != PreviousSize) { + auto &Element = Signature.back(); + Element.InterpMode = llvm::hlsl::normalizeInterpolationMode( + llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType, + Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType()); + } + return std::make_pair(Value, AttrBegin); } specific_attr_iterator<HLSLAppliedSemanticAttr> diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index f551bb006ea2c..90a4940a03832 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -256,7 +256,8 @@ class CGHLSLRuntime { const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> begin, specific_attr_iterator<HLSLAppliedSemanticAttr> end, - SemanticSignatures &Signature); + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers); specific_attr_iterator<HLSLAppliedSemanticAttr> handleStructSemanticStore( llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source, @@ -270,7 +271,9 @@ class CGHLSLRuntime { llvm::Type *Type, const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> begin, specific_attr_iterator<HLSLAppliedSemanticAttr> end, - SemanticSignatures &Signature); + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers = + llvm::hlsl::InterpolationModifier::None); specific_attr_iterator<HLSLAppliedSemanticAttr> handleSemanticStore(llvm::IRBuilder<> &B, const FunctionDecl *FD, diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl new file mode 100644 index 0000000000000..fbabd8d4704c7 --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/interpolation-aggregate.hlsl @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +struct Inner { + float inherited; + linear float overridden; +}; +struct Outer { + Inner inherited; + sample noperspective Inner overridden; + float2 array[2][3]; +}; + +[shader("pixel")] +float4 main(nointerpolation Outer a : A, centroid Outer b : B, + Outer c : C) : SV_Target { + return 0; +} + +// Each use of Outer inherits independently. Inner's explicit linear replaces +// the entire inherited mask, and a field's modifier does not affect siblings. + +// CHECK-DAG: !{i32 0, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 1, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 2, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 4, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 6, i8 2, +// CHECK-DAG: !{i32 5, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 6, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 7, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1, +// CHECK-DAG: !{i32 8, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 9, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 6, i8 2, +// CHECK-DAG: !{i32 10, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 11, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 12, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1, +// CHECK-DAG: !{i32 13, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 14, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 6, i8 2, +// ^ Interpolation mode. + +struct Position { + float4 p : SV_Position; +}; +[shader("pixel")] +float4 position(sample Position p) : SV_Target { return p.p; } +// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 1, i8 4, +// ^ Interpolation mode. diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl new file mode 100644 index 0000000000000..88c46a15571cf --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl @@ -0,0 +1,99 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -Wno-ignored-attributes -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s + +[shader("pixel")] +float4 modes(float a : A, + nointerpolation float b : B, + linear float c : C, + centroid float d : D, + noperspective float e : E, + noperspective centroid float f : F, + sample float g : G, + noperspective sample float h : H, + center float i : I, + linear center float j : J, + linear centroid float k : K, + linear noperspective center float l : L, + linear noperspective centroid float m : M, + linear sample float n : N, + sample linear noperspective float o : O) : SV_Target { + return 0; +} + +// CHECK-DAG: !{i32 0, !"A", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 1, !"B", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 2, !"C", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"D", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 4, !"E", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 1, +// CHECK-DAG: !{i32 5, !"F", i32 9, i32 0, !{{[0-9]+}}, i32 5, i32 1, i8 1, +// CHECK-DAG: !{i32 6, !"G", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// CHECK-DAG: !{i32 7, !"H", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1, +// CHECK-DAG: !{i32 8, !"I", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 9, !"J", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 10, !"K", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 11, !"L", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 1, +// CHECK-DAG: !{i32 12, !"M", i32 9, i32 0, !{{[0-9]+}}, i32 5, i32 1, i8 1, +// CHECK-DAG: !{i32 13, !"N", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// CHECK-DAG: !{i32 14, !"O", i32 9, i32 0, !{{[0-9]+}}, i32 7, i32 1, i8 1, +// ^ Interpolation mode. + +[shader("pixel")] +sample float4 defaults(int a : INT, uint2 b : UINT, bool c : BOOL, + double d : DOUBLE, nointerpolation float2 e : FLAT, + half f : HALF) : SV_Target { + return 0; +} +// CHECK-DAG: !{i32 0, !"INT", i32 4, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 1, !"UINT", i32 5, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 2, +// CHECK-DAG: !{i32 2, !"BOOL", i32 {{[0-9]+}}, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"DOUBLE", i32 10, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 4, !"FLAT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 2, +// CHECK-DAG: !{i32 5, !"HALF", i32 {{[89]}}, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// ^ Interpolation mode. +// Pixel outputs remain Undefined, including explicitly qualified returns. +// CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, i8 4, +// ^ Interpolation mode. + +[shader("pixel")] +float4 positions(float4 a : SV_Position0, + centroid float4 b : SV_Position1, + sample float4 c : SV_Position2, + noperspective sample float4 d : SV_Position3) : SV_Target { + return a + b + c + d; +} +// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 4, i32 1, i8 4, +// CHECK-DAG: !{i32 1, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 5, i32 1, i8 4, +// CHECK-DAG: !{i32 2, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 1, i8 4, +// CHECK-DAG: !{i32 3, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 7, i32 1, i8 4, +// ^ Interpolation mode. + +[shader("pixel")] +float4 precedence(center centroid float a : CENTER_CENTROID, + centroid center float b : CENTROID_CENTER, + center sample float c : CENTER_SAMPLE, + sample center float d : SAMPLE_CENTER, + centroid sample float e : CENTROID_SAMPLE, + sample centroid float f : SAMPLE_CENTROID) : SV_Target { + return 0; +} + +// Sampling-location precedence is independent of keyword order. + +// CHECK-DAG: !{i32 0, !"CENTER_CENTROID", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 1, !"CENTROID_CENTER", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 2, !"CENTER_SAMPLE", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"SAMPLE_CENTER", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// CHECK-DAG: !{i32 4, !"CENTROID_SAMPLE", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// CHECK-DAG: !{i32 5, !"SAMPLE_CENTROID", i32 9, i32 0, !{{[0-9]+}}, i32 6, i32 1, i8 1, +// ^ Interpolation mode. + +[shader("vertex")] +centroid float4 vertex(sample float4 a : VERTEX, + nointerpolation uint b : SV_VertexID) : SV_Position { + return a; +} + +// Non-pixel signatures remain Undefined even with explicit modifiers. +// CHECK-DAG: !{i32 0, !"VERTEX", i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 1, i8 4, +// CHECK-DAG: !{i32 1, !"SV_VertexID", i32 5, i32 1, !{{[0-9]+}}, i32 0, i32 1, i8 1, +// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 0, i32 1, i8 4, +// ^ Interpolation mode. diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h index ceb40c806c958..d6d57edd6ffae 100644 --- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h +++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h @@ -80,6 +80,13 @@ getInterpolationSamplingLocation(InterpolationModifier Modifiers); LLVM_ABI dxbc::PSV::InterpolationMode getInterpolationMode(InterpolationModifier Modifiers); +// Apply component-type defaults, SV_Position's noperspective requirement, and +// the shader-stage restriction. Only pixel shader inputs are interpolated; +// callers should leave output signatures Undefined. +LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode( + dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType, + dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage); + enum class SemanticInterpretation { Invalid, NotAllocated, diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp index 4bdef5bd02ffa..eccab31d272d4 100644 --- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp +++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp @@ -81,6 +81,43 @@ hlsl::getInterpolationMode(InterpolationModifier Modifiers) { } } +dxbc::PSV::InterpolationMode hlsl::normalizeInterpolationMode( + dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType, + dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage) { + if (Stage != Triple::Pixel) + return InterpolationMode::Undefined; + + if (Mode == InterpolationMode::Undefined) { + switch (CompType) { + case dxil::ElementType::F16: + case dxil::ElementType::F32: + case dxil::ElementType::SNormF16: + case dxil::ElementType::UNormF16: + case dxil::ElementType::SNormF32: + case dxil::ElementType::UNormF32: + Mode = InterpolationMode::Linear; + break; + default: + Mode = InterpolationMode::Constant; + break; + } + } + + if (Kind == dxbc::PSV::SemanticKind::Position) { + switch (Mode) { + case InterpolationMode::Linear: + return InterpolationMode::LinearNoperspective; + case InterpolationMode::LinearCentroid: + return InterpolationMode::LinearNoperspectiveCentroid; + case InterpolationMode::LinearSample: + return InterpolationMode::LinearNoperspectiveSample; + default: + break; + } + } + return Mode; +} + dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) { if (!SemanticName.consume_front_insensitive("SV_")) return dxbc::PSV::SemanticKind::Arbitrary; diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp index 354475c5a7e5f..d4c6bf623e5cf 100644 --- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -13,6 +13,8 @@ using namespace llvm; using namespace llvm::hlsl; using Mod = InterpolationModifier; using InterpMode = dxbc::PSV::InterpolationMode; +using SemanticKind = dxbc::PSV::SemanticKind; +using CompType = dxil::ElementType; TEST(HLSLInterpolationTest, AllModifierSets) { // Cover every subset, including redundant linear/center modifiers and all @@ -70,3 +72,56 @@ TEST(HLSLInterpolationTest, SamplingLocations) { EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected); } } + +TEST(HLSLInterpolationTest, ComponentDefaults) { + for (CompType Type : + {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16, + CompType::SNormF32, CompType::UNormF32}) + EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, + SemanticKind::Arbitrary, + Triple::Pixel), + InterpMode::Linear); + for (CompType Type : + {CompType::I1, CompType::I16, CompType::U16, CompType::I32, + CompType::U32, CompType::I64, CompType::U64, CompType::F64, + CompType::SNormF64, CompType::UNormF64}) + EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, + SemanticKind::Arbitrary, + Triple::Pixel), + InterpMode::Constant); +} + +TEST(HLSLInterpolationTest, PositionAndExplicitModes) { + constexpr InterpMode PositionModes[] = { + InterpMode::LinearNoperspective, + InterpMode::Constant, + InterpMode::LinearNoperspective, + InterpMode::LinearNoperspectiveCentroid, + InterpMode::LinearNoperspective, + InterpMode::LinearNoperspectiveCentroid, + InterpMode::LinearNoperspectiveSample, + InterpMode::LinearNoperspectiveSample, + InterpMode::Invalid}; + for (unsigned I = 0; I != 9; ++I) { + auto Mode = static_cast<InterpMode>(I); + EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32, + SemanticKind::Position, Triple::Pixel), + PositionModes[I]); + if (Mode != InterpMode::Undefined) + EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32, + SemanticKind::Arbitrary, + Triple::Pixel), + Mode); + } +} + +TEST(HLSLInterpolationTest, OtherStages) { + for (auto Stage : + {Triple::Vertex, Triple::Geometry, Triple::Hull, Triple::Domain, + Triple::Mesh, Triple::Compute, Triple::Amplification, Triple::Library}) + for (unsigned I = 0; I != 9; ++I) + for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position}) + EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I), + CompType::F32, Kind, Stage), + InterpMode::Undefined); +} >From a15e027e388a807606c16960f1f8364bcbc04328 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 24 Sep 2026 16:33:32 +0000 Subject: [PATCH 04/10] lower for vertex outputs --- clang/include/clang/Basic/AttrDocs.td | 18 ++-- clang/lib/CodeGen/CGHLSLRuntime.cpp | 31 ++++-- clang/lib/CodeGen/CGHLSLRuntime.h | 7 +- clang/lib/Sema/SemaHLSL.cpp | 18 ++-- .../semantics/interpolation-vs-ps-link.hlsl | 53 +++++++++ .../CodeGenHLSL/semantics/interpolation.hlsl | 5 +- .../semantics/semantic.output.hlsl | 8 +- .../interpolation-modifiers-vertex.hlsl | 102 ++++++++++++++++++ .../llvm/Frontend/HLSL/SemanticSignatures.h | 8 +- llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 5 +- .../Frontend/HLSLInterpolationTest.cpp | 80 ++++++++------ 11 files changed, 266 insertions(+), 69 deletions(-) create mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl create mode 100644 clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index fa8e2c0502f66..38eca6ad292a2 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -9390,20 +9390,22 @@ def HLSLInterpolationModifierDocs : Documentation { let Content = [{ The HLSL keywords `nointerpolation`, `linear`, `centroid`, `noperspective`, `sample`, and `center` control interpolation of pixel -shader inputs. +shader inputs and vertex shader outputs. When applied to an aggregate type, the modifier propagates recursively to every scalar or vector of the type. A modifier on an inner field overrides one inherited from an enclosing declaration. -`nointerpolation` cannot be combined with another interpolation modifier and -cannot be used on `SV_Position`. Integer, boolean, and 64-bit floating-point -input components only support `nointerpolation`. +`nointerpolation` cannot be combined with another interpolation modifier. +For pixel shader inputs and vertex shader outputs, it cannot be used on +`SV_Position`, and integer, boolean, and 64-bit floating-point components only +support `nointerpolation`. -Unqualified pixel shader inputs default to `linear` for floating-point -components of at most 32 bits and `nointerpolation` otherwise. `SV_Position` -uses the corresponding `noperspective` mode. Other shader stages and output -signatures have no interpolation mode and are ignored. +Unqualified pixel shader inputs and vertex shader outputs default to `linear` +for floating-point components of at most 32 bits and `nointerpolation` otherwise. +`SV_Position` uses the corresponding `noperspective` mode. Vertex shader inputs, +pixel shader outputs, and signatures in other shader stages have no +interpolation mode; their interpolation modifiers are ignored. }]; } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp index a15b3490b8827..11f4825a08a81 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1299,8 +1299,8 @@ static llvm::hlsl::SemanticSignatureElement createSemanticSignatureElement( SemanticIndices.push_back(FirstSemanticIndex + I); // The remaining members keep their default value and will be filled at a - // later stage, either during packing or analysis of usage. Input - // interpolation is set by handleSemanticLoad after visiting each leaf. + // later stage, either during packing or analysis of usage. Interpolation + // is set by the load/store traversal after visiting each leaf. return llvm::hlsl::SemanticSignatureElement( SigId, Name, getSignatureComponentType(CGM, Shape.RowType), llvm::hlsl::getSemanticKind(Name), SemanticIndices, @@ -1690,7 +1690,8 @@ CGHLSLRuntime::handleStructSemanticStore( const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature) { + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers) { const llvm::StructType *ST = cast<StructType>(Source->getType()); @@ -1707,7 +1708,7 @@ CGHLSLRuntime::handleStructSemanticStore( for (unsigned I = 0; I < ST->getNumElements(); ++I, ++FieldDecl) { llvm::Value *Extract = B.CreateExtractValue(Source, I); AttrBegin = handleSemanticStore(B, FD, Extract, *FieldDecl, AttrBegin, - AttrEnd, Signature); + AttrEnd, Signature, Modifiers); } return AttrBegin; @@ -1737,12 +1738,13 @@ CGHLSLRuntime::handleSemanticLoad( llvm::Value *Value = handleScalarSemanticLoad(B, FD, Type, Decl, Attr, Signature); // Intrinsic-only system values and SPIR-V loads do not add DXIL signature - // elements. Outputs are not interpolated and retain Undefined. + // elements. Non-pixel inputs retain Undefined. if (Signature.size() != PreviousSize) { auto &Element = Signature.back(); Element.InterpMode = llvm::hlsl::normalizeInterpolationMode( llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType, - Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType()); + Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType(), + llvm::hlsl::IOType::In); } return std::make_pair(Value, AttrBegin); } @@ -1753,15 +1755,28 @@ CGHLSLRuntime::handleSemanticStore( const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature) { + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers) { assert(AttrBegin != AttrEnd); + // An inner field overrides the enclosing return declaration's modifiers. + if (const auto *A = Decl->getAttr<HLSLInterpolationModifierAttr>()) + Modifiers = + static_cast<llvm::hlsl::InterpolationModifier>(A->getModifiers()); if (Source->getType()->isStructTy()) return handleStructSemanticStore(B, FD, Source, Decl, AttrBegin, AttrEnd, - Signature); + Signature, Modifiers); HLSLAppliedSemanticAttr *Attr = *AttrBegin; ++AttrBegin; + size_t PreviousSize = Signature.size(); handleScalarSemanticStore(B, FD, Source, Decl, Attr, Signature); + if (Signature.size() != PreviousSize) { + auto &Element = Signature.back(); + Element.InterpMode = llvm::hlsl::normalizeInterpolationMode( + llvm::hlsl::getInterpolationMode(Modifiers), Element.CompType, + Element.SemanticKind, FD->getAttr<HLSLShaderAttr>()->getType(), + llvm::hlsl::IOType::Out); + } return AttrBegin; } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h index 90a4940a03832..7aa97841331a6 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -264,7 +264,8 @@ class CGHLSLRuntime { const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature); + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers); std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>> handleSemanticLoad(llvm::IRBuilder<> &B, const FunctionDecl *FD, @@ -280,7 +281,9 @@ class CGHLSLRuntime { llvm::Value *Source, const clang::DeclaratorDecl *Decl, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin, specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd, - SemanticSignatures &Signature); + SemanticSignatures &Signature, + llvm::hlsl::InterpolationModifier Modifiers = + llvm::hlsl::InterpolationModifier::None); public: CGHLSLRuntime(CodeGenModule &CGM) : CGM(CGM) {} diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 17664ca7451df..690896b712ec9 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -910,8 +910,9 @@ bool SemaHLSL::checkInterpolationModifiers( if (!Semantic) Semantic = D->getAttr<HLSLParsedSemanticAttr>(); - QualType T = - getASTContext().getBaseElementType(D->getType().getNonReferenceType()); + const auto *FD = dyn_cast<FunctionDecl>(D); + QualType T = FD ? FD->getReturnType() : D->getType(); + T = getASTContext().getBaseElementType(T.getNonReferenceType()); if (T->isDependentType()) return true; if (const auto *RT = T->getAs<RecordType>()) { @@ -1197,9 +1198,10 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) { const auto *MA = Param->getAttr<HLSLParamModifierAttr>(); SemanticContext &SC = MA && MA->isAnyOut() ? OutputSC : InputSC; - // Interpolation only applies to pixel shader inputs, including the input - // side of inout parameters. Other stages and output signatures ignore it. - if (ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn()) && + // Interpolation applies to pixel inputs and vertex outputs, including the + // corresponding side of inout parameters. + if (((ST == llvm::Triple::Pixel && (!MA || MA->isAnyIn())) || + (ST == llvm::Triple::Vertex && MA && MA->isAnyOut())) && !checkInterpolationModifiers(Param, nullptr, nullptr)) FD->setInvalidDecl(); @@ -1213,8 +1215,12 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) { ActiveSemantic.Semantic = FD->getAttr<HLSLParsedSemanticAttr>(); if (ActiveSemantic.Semantic) ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex(); - if (!FD->getReturnType()->isVoidType()) + if (!FD->getReturnType()->isVoidType()) { + if (ST == llvm::Triple::Vertex && + !checkInterpolationModifiers(FD, nullptr, nullptr)) + FD->setInvalidDecl(); determineActiveSemantic(FD, FD, FD, ActiveSemantic, OutputSC); + } } void SemaHLSL::checkSemanticAnnotation( diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl new file mode 100644 index 0000000000000..1e118bc27fcdc --- /dev/null +++ b/clang/test/CodeGenHLSL/semantics/interpolation-vs-ps-link.hlsl @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s + +struct Nested { + float inherited : INHERIT; + linear float overrideMode : OVERRIDE; +}; + +struct Varyings { + float4 pos : SV_Position; + float value : LIN; + nointerpolation float flat : FLAT; + noperspective float screen : NOPERS; + centroid float cent : CENTRO; + uint face : FACEID; + nointerpolation Nested nested; +}; + +[shader("vertex")] +Varyings vs(float4 pos : POSITION, float value : VAL) { + Varyings result; + result.pos = pos; + result.value = value; + result.flat = value; + result.screen = value; + result.cent = value; + result.face = 1; + result.nested.inherited = value; + result.nested.overrideMode = value; + return result; +} + +[shader("pixel")] +float4 ps(Varyings input) : SV_Target { + return float4(input.value, input.flat, input.screen, + input.nested.inherited + input.nested.overrideMode); +} + +// Vertex outputs and pixel inputs must have identical packed signatures, +// including their interpolation modes; only then can the shaders link. +// CHECK: !dx.semantic.signatures = !{![[VS:[0-9]+]], ![[PS:[0-9]+]]} +// CHECK-DAG: ![[VS]] = !{ptr @vs, ![[ATTR:[0-9]+]], ![[VARY:[0-9]+]]} +// CHECK-DAG: ![[PS]] = !{ptr @ps, ![[VARY]], ![[TARGET:[0-9]+]]} +// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 4, i32 1, i8 4, +// CHECK-DAG: !{i32 1, !"LIN", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 2, !"FLAT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"NOPERS", i32 9, i32 0, !{{[0-9]+}}, i32 4, i32 1, i8 1, +// CHECK-DAG: !{i32 4, !"CENTRO", i32 9, i32 0, !{{[0-9]+}}, i32 3, i32 1, i8 1, +// CHECK-DAG: !{i32 5, !"FACEID", i32 5, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 6, !"INHERIT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, +// CHECK-DAG: !{i32 7, !"OVERRIDE", i32 9, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// Pixel outputs and vertex inputs do not use interpolation modes. +// CHECK-DAG: !{i32 0, !"POSITION", i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 1, i8 4, +// CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, i8 4, diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl index 88c46a15571cf..1a242eab21edb 100644 --- a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl +++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl @@ -92,8 +92,9 @@ centroid float4 vertex(sample float4 a : VERTEX, return a; } -// Non-pixel signatures remain Undefined even with explicit modifiers. +// Vertex inputs remain Undefined; vertex outputs carry the interpolation mode +// used to pack against pixel inputs. SV_Position is noperspective. // CHECK-DAG: !{i32 0, !"VERTEX", i32 9, i32 0, !{{[0-9]+}}, i32 0, i32 1, i8 4, // CHECK-DAG: !{i32 1, !"SV_VertexID", i32 5, i32 1, !{{[0-9]+}}, i32 0, i32 1, i8 1, -// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 0, i32 1, i8 4, +// CHECK-DAG: !{i32 0, !"SV_Position", i32 9, i32 3, !{{[0-9]+}}, i32 5, i32 1, i8 4, // ^ Interpolation mode. diff --git a/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl index 54e9aa83cda0b..9be9c6739112f 100644 --- a/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl +++ b/clang/test/CodeGenHLSL/semantics/semantic.output.hlsl @@ -58,10 +58,10 @@ S main() { // CHECK: !dx.semantic.signatures = !{![[#ENTRY_SIG:]]} // CHECK: ![[#ENTRY_SIG]] = !{ptr @main, null, ![[#OUTPUT_SIG:]]} // CHECK: ![[#OUTPUT_SIG]] = !{![[#A_SIG:]], ![[#B_SIG:]], ![[#D_SIG:]], ![[#E_SIG:]]} -// CHECK: ![[#A_SIG]] = !{i32 0, !"A", i32 9, i32 0, ![[#ZERO_INDEX:]], i32 0, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// CHECK: ![[#A_SIG]] = !{i32 0, !"A", i32 9, i32 0, ![[#ZERO_INDEX:]], i32 2, i32 1, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} // CHECK: ![[#ZERO_INDEX]] = !{i32 0} -// CHECK: ![[#B_SIG]] = !{i32 1, !"B", i32 9, i32 0, ![[#ZERO_INDEX]], i32 0, i32 1, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0} -// CHECK: ![[#D_SIG]] = !{i32 2, !"D", i32 9, i32 0, ![[#D_INDICES:]], i32 0, i32 5, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// CHECK: ![[#B_SIG]] = !{i32 1, !"B", i32 9, i32 0, ![[#ZERO_INDEX]], i32 2, i32 1, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// CHECK: ![[#D_SIG]] = !{i32 2, !"D", i32 9, i32 0, ![[#D_INDICES:]], i32 2, i32 5, i8 1, i32 -1, i8 -1, i8 0, i8 0, i32 0} // CHECK: ![[#D_INDICES]] = !{i32 0, i32 1, i32 2, i32 3, i32 4} -// CHECK: ![[#E_SIG]] = !{i32 3, !"E", i32 9, i32 0, ![[#E_INDICES:]], i32 0, i32 6, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0} +// CHECK: ![[#E_SIG]] = !{i32 3, !"E", i32 9, i32 0, ![[#E_INDICES:]], i32 2, i32 6, i8 4, i32 -1, i8 -1, i8 0, i8 0, i32 0} // CHECK: ![[#E_INDICES]] = !{i32 0, i32 1, i32 2, i32 3, i32 4, i32 5} diff --git a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl new file mode 100644 index 0000000000000..fee5422635011 --- /dev/null +++ b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -finclude-default-header -verify %s + +// Vertex return declarations have the same interpolation restrictions as pixel +// inputs. Check the return type, not the function type. +[shader("vertex")] +// expected-error@+1 {{cannot be used with type 'uint'}} +linear uint invalid_integer() : VALUE { return 0; } +[shader("vertex")] +// expected-error@+1 {{cannot be used with type 'bool'}} +centroid bool invalid_bool() : VALUE { return false; } +[shader("vertex")] +// expected-error@+1 {{cannot be used with type 'double'}} +sample double invalid_double() : VALUE { return 0; } +[shader("vertex")] +// expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}} +// expected-note@+1 {{conflicting attribute is here}} +nointerpolation float4 invalid_position() : SV_Position { return 0; } + +// Unqualified aggregates must still validate modifiers on nested fields. +struct InvalidField { + // expected-error@+1 {{cannot be used with type 'uint'}} + linear uint value : VALUE; +}; +struct InvalidOuter { + float4 pos : SV_Position; + InvalidField inner; +}; +[shader("vertex")] +InvalidOuter invalid_nested_field() { return (InvalidOuter)0; } + +// A return modifier propagates through nested aggregates and array leaves. +struct IntegerLeaf { + int values[2] : VALUE; +}; +struct IntegerOuter { + IntegerLeaf inner; +}; +[shader("vertex")] +// expected-error@+1 {{cannot be used with type 'int'}} +linear IntegerOuter invalid_inherited_type() { return (IntegerOuter)0; } + +struct PositionLeaf { + // expected-note@+1 {{conflicting attribute is here}} + float4 pos : SV_Position; +}; +struct PositionOuter { + PositionLeaf inner; +}; +[shader("vertex")] +// expected-error@+1 {{'nointerpolation' cannot be used on SV_Position}} +nointerpolation PositionOuter invalid_inherited_position() { + return (PositionOuter)0; +} + +struct FlatPosition { + // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}} + // expected-note@+1 {{conflicting attribute is here}} + nointerpolation float4 pos : SV_Position; +}; +[shader("vertex")] +FlatPosition invalid_position_field() { return (FlatPosition)0; } + +// The output side of out and inout parameters also needs validation. +[shader("vertex")] +void invalid_output_parameters( + // expected-error@+1 {{cannot be used with type 'uint'}} + linear out uint a : A, + // expected-error@+1 {{cannot be used with type 'int'}} + sample inout int b : B) { + a = 0; +} + +// Vertex inputs and non-entry declarations still ignore these restrictions. +linear uint helper() { return 0; } +[shader("vertex")] +float4 valid_input(linear uint a : VALUE) : SV_Position { return 0; } + +[shader("vertex")] +nointerpolation uint valid_integer() : VALUE { return 0; } +[shader("vertex")] +nointerpolation bool valid_bool() : VALUE { return false; } +[shader("vertex")] +nointerpolation double valid_double() : VALUE { return 0; } + +// Inner modifiers replace the entire inherited set, including the return's +// modifier. Neither the integer nor the position should inherit sample/flat. +struct OverrideLeaf { + nointerpolation uint value : VALUE; + linear float4 pos : SV_Position; +}; +struct OverrideOuter { + nointerpolation OverrideLeaf inner; + float other : OTHER; +}; +[shader("vertex")] +sample OverrideOuter valid_overrides() { return (OverrideOuter)0; } + +[shader("vertex")] +void valid_output_parameters(nointerpolation out uint a : A, + nointerpolation inout int b : B) { + a = 0; +} diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h index d6d57edd6ffae..f16d4b1de436f 100644 --- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h +++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h @@ -80,12 +80,12 @@ getInterpolationSamplingLocation(InterpolationModifier Modifiers); LLVM_ABI dxbc::PSV::InterpolationMode getInterpolationMode(InterpolationModifier Modifiers); -// Apply component-type defaults, SV_Position's noperspective requirement, and -// the shader-stage restriction. Only pixel shader inputs are interpolated; -// callers should leave output signatures Undefined. +// Apply component-type defaults and SV_Position's noperspective requirement. +// Vertex outputs carry interpolation modes so they pack compatibly with pixel +// inputs; other signature points do not use interpolation modes. LLVM_ABI dxbc::PSV::InterpolationMode normalizeInterpolationMode( dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType, - dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage); + dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage, IOType IO); enum class SemanticInterpretation { Invalid, diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp index eccab31d272d4..acade318f1ca5 100644 --- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp +++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp @@ -83,8 +83,9 @@ hlsl::getInterpolationMode(InterpolationModifier Modifiers) { dxbc::PSV::InterpolationMode hlsl::normalizeInterpolationMode( dxbc::PSV::InterpolationMode Mode, dxil::ElementType CompType, - dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage) { - if (Stage != Triple::Pixel) + dxbc::PSV::SemanticKind Kind, Triple::EnvironmentType Stage, IOType IO) { + if (!((Stage == Triple::Pixel && IO == IOType::In) || + (Stage == Triple::Vertex && IO == IOType::Out))) return InterpolationMode::Undefined; if (Mode == InterpolationMode::Undefined) { diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp index d4c6bf623e5cf..af8f79c052e3b 100644 --- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -74,21 +74,23 @@ TEST(HLSLInterpolationTest, SamplingLocations) { } TEST(HLSLInterpolationTest, ComponentDefaults) { - for (CompType Type : - {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16, - CompType::SNormF32, CompType::UNormF32}) - EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, - SemanticKind::Arbitrary, - Triple::Pixel), - InterpMode::Linear); - for (CompType Type : - {CompType::I1, CompType::I16, CompType::U16, CompType::I32, - CompType::U32, CompType::I64, CompType::U64, CompType::F64, - CompType::SNormF64, CompType::UNormF64}) - EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, - SemanticKind::Arbitrary, - Triple::Pixel), - InterpMode::Constant); + for (auto Stage : {Triple::Pixel, Triple::Vertex}) { + SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str()); + IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out; + for (CompType Type : + {CompType::F16, CompType::F32, CompType::SNormF16, CompType::UNormF16, + CompType::SNormF32, CompType::UNormF32}) + EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, + SemanticKind::Arbitrary, Stage, IO), + InterpMode::Linear); + for (CompType Type : + {CompType::I1, CompType::I16, CompType::U16, CompType::I32, + CompType::U32, CompType::I64, CompType::U64, CompType::F64, + CompType::SNormF64, CompType::UNormF64}) + EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, + SemanticKind::Arbitrary, Stage, IO), + InterpMode::Constant); + } } TEST(HLSLInterpolationTest, PositionAndExplicitModes) { @@ -102,26 +104,38 @@ TEST(HLSLInterpolationTest, PositionAndExplicitModes) { InterpMode::LinearNoperspectiveSample, InterpMode::LinearNoperspectiveSample, InterpMode::Invalid}; - for (unsigned I = 0; I != 9; ++I) { - auto Mode = static_cast<InterpMode>(I); - EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32, - SemanticKind::Position, Triple::Pixel), - PositionModes[I]); - if (Mode != InterpMode::Undefined) + for (auto Stage : {Triple::Pixel, Triple::Vertex}) { + SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str()); + IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out; + for (unsigned I = 0; I != 9; ++I) { + SCOPED_TRACE(I); + auto Mode = static_cast<InterpMode>(I); EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32, - SemanticKind::Arbitrary, - Triple::Pixel), - Mode); + SemanticKind::Position, Stage, IO), + PositionModes[I]); + if (Mode != InterpMode::Undefined) + EXPECT_EQ(normalizeInterpolationMode( + Mode, CompType::F32, SemanticKind::Arbitrary, Stage, IO), + Mode); + } } } -TEST(HLSLInterpolationTest, OtherStages) { - for (auto Stage : - {Triple::Vertex, Triple::Geometry, Triple::Hull, Triple::Domain, - Triple::Mesh, Triple::Compute, Triple::Amplification, Triple::Library}) - for (unsigned I = 0; I != 9; ++I) - for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position}) - EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I), - CompType::F32, Kind, Stage), - InterpMode::Undefined); +TEST(HLSLInterpolationTest, NonInterpolatedSignatures) { + for (auto Stage : {Triple::Pixel, Triple::Vertex, Triple::Geometry, + Triple::Hull, Triple::Domain, Triple::Mesh, + Triple::Compute, Triple::Amplification, Triple::Library}) { + SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str()); + for (auto IO : {IOType::In, IOType::Out}) { + SCOPED_TRACE(static_cast<unsigned>(IO)); + if ((Stage == Triple::Pixel && IO == IOType::In) || + (Stage == Triple::Vertex && IO == IOType::Out)) + continue; + for (unsigned I = 0; I != 9; ++I) + for (auto Kind : {SemanticKind::Arbitrary, SemanticKind::Position}) + EXPECT_EQ(normalizeInterpolationMode(static_cast<InterpMode>(I), + CompType::F32, Kind, Stage, IO), + InterpMode::Undefined); + } + } } >From 86cf88c55bce8747ea7c3133c649e546e110de39 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 22 Sep 2026 16:23:56 +0000 Subject: [PATCH 05/10] nfc: remove unused isHLSLQualifier --- clang/include/clang/Parse/Parser.h | 1 - clang/lib/Parse/ParseDecl.cpp | 7 ------- 2 files changed, 8 deletions(-) diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 6de876b151097..62ad82bd1cb73 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2379,7 +2379,6 @@ class Parser : public CodeCompletionHandler { void ParseOpenCLQualifiers(ParsedAttributes &Attrs); void ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs); void ParseCUDAFunctionAttributes(ParsedAttributes &attrs); - bool isHLSLQualifier(const Token &Tok) const; void ParseHLSLQualifiers(ParsedAttributes &Attrs); /// Parse a version number. diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 6fb777a9416bc..913cadd175fbc 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -1092,13 +1092,6 @@ void Parser::ParseOpenCLQualifiers(ParsedAttributes &Attrs) { Tok.getKind()); } -bool Parser::isHLSLQualifier(const Token &Tok) const { - return Tok.is(tok::kw_groupshared) || Tok.is(tok::kw_row_major) || - Tok.is(tok::kw_column_major) || - Tok.isOneOf(tok::kw_nointerpolation, tok::kw_linear, tok::kw_centroid, - tok::kw_noperspective, tok::kw_sample, tok::kw_center); -} - void Parser::ParseHLSLQualifiers(ParsedAttributes &Attrs) { IdentifierInfo *AttrName = Tok.getIdentifierInfo(); auto Kind = Tok.getKind(); >From 0528f31b4874128afb44b6bd9325318cd0085027 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 22 Sep 2026 16:30:38 +0000 Subject: [PATCH 06/10] removed unused file --- clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl diff --git a/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation-no-entry.hlsl deleted file mode 100644 index e69de29bb2d1d..0000000000000 >From 94dae80279da473aa2b90b5405a4f8989def8826 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Tue, 22 Sep 2026 16:35:18 +0000 Subject: [PATCH 07/10] self-review: use %select for interpolation override diagnostics --- .../clang/Basic/DiagnosticSemaKinds.td | 3 ++- clang/lib/Sema/SemaHLSL.cpp | 22 ++++--------------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c84e9a18cadd6..c271ff92b12eb 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13716,7 +13716,8 @@ def warn_hlsl_duplicate_interpolation : Warning< "duplicate interpolation modifier %0">, InGroup<DuplicateDeclSpecifier>; def warn_hlsl_interpolation_override : Warning< - "interpolation modifier '%0' overrides '%1'">, + "interpolation modifier '%select{centroid|sample}0' overrides " + "'%select{center|centroid}1'">, InGroup<IgnoredAttributes>; def err_hlsl_missing_semantic_annotation : Error< "semantic annotations must be present for all parameters of an entry " diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 690896b712ec9..ab5d4b31bccad 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -827,20 +827,6 @@ SemaHLSL::mergeParamModifierAttr(Decl *D, const AttributeCommonInfo &AL, return HLSLParamModifierAttr::Create(getASTContext(), AL); } -static StringRef -getInterpolationSamplingLocationName(InterpolationModifier Location) { - switch (Location) { - case InterpolationModifier::Sample: - return "sample"; - case InterpolationModifier::Centroid: - return "centroid"; - case InterpolationModifier::Center: - return "center"; - default: - llvm_unreachable("expected an explicit interpolation sampling location"); - } -} - void SemaHLSL::handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL) { InterpolationModifier Modifier; switch (static_cast<HLSLInterpolationModifierAttr::Spelling>( @@ -887,10 +873,10 @@ void SemaHLSL::handleInterpolationModifierAttr(Decl *D, const ParsedAttr &AL) { llvm::hlsl::getInterpolationSamplingLocation(Modifier); if (any(OldLocation) && any(NewLocation)) { Diag(AL.getLoc(), diag::warn_hlsl_interpolation_override) - << getInterpolationSamplingLocationName( - std::max(OldLocation, NewLocation)) - << getInterpolationSamplingLocationName( - std::min(OldLocation, NewLocation)); + << (std::max(OldLocation, NewLocation) == + InterpolationModifier::Sample) + << (std::min(OldLocation, NewLocation) == + InterpolationModifier::Centroid); } } D->dropAttr<HLSLInterpolationModifierAttr>(); >From ee0621dd2315613346dcadc33817efd2b2c0fb83 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 24 Sep 2026 16:59:44 +0000 Subject: [PATCH 08/10] self-review: remove unsupported 64-bit signature test cases --- clang/test/CodeGenHLSL/semantics/interpolation.hlsl | 9 ++++----- .../test/SemaHLSL/interpolation-modifiers-vertex.hlsl | 5 ----- clang/test/SemaHLSL/interpolation-modifiers.hlsl | 10 ++++------ llvm/unittests/Frontend/HLSLInterpolationTest.cpp | 6 ++---- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl index 1a242eab21edb..5f39c2d2af8aa 100644 --- a/clang/test/CodeGenHLSL/semantics/interpolation.hlsl +++ b/clang/test/CodeGenHLSL/semantics/interpolation.hlsl @@ -38,16 +38,15 @@ float4 modes(float a : A, [shader("pixel")] sample float4 defaults(int a : INT, uint2 b : UINT, bool c : BOOL, - double d : DOUBLE, nointerpolation float2 e : FLAT, - half f : HALF) : SV_Target { + nointerpolation float2 e : FLAT, half f : HALF) + : SV_Target { return 0; } // CHECK-DAG: !{i32 0, !"INT", i32 4, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, // CHECK-DAG: !{i32 1, !"UINT", i32 5, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 2, // CHECK-DAG: !{i32 2, !"BOOL", i32 {{[0-9]+}}, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, -// CHECK-DAG: !{i32 3, !"DOUBLE", i32 10, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 1, -// CHECK-DAG: !{i32 4, !"FLAT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 2, -// CHECK-DAG: !{i32 5, !"HALF", i32 {{[89]}}, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, +// CHECK-DAG: !{i32 3, !"FLAT", i32 9, i32 0, !{{[0-9]+}}, i32 1, i32 1, i8 2, +// CHECK-DAG: !{i32 4, !"HALF", i32 {{[89]}}, i32 0, !{{[0-9]+}}, i32 2, i32 1, i8 1, // ^ Interpolation mode. // Pixel outputs remain Undefined, including explicitly qualified returns. // CHECK-DAG: !{i32 0, !"SV_Target", i32 9, i32 16, !{{[0-9]+}}, i32 0, i32 1, i8 4, diff --git a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl index fee5422635011..997aef970d84f 100644 --- a/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl +++ b/clang/test/SemaHLSL/interpolation-modifiers-vertex.hlsl @@ -9,9 +9,6 @@ linear uint invalid_integer() : VALUE { return 0; } // expected-error@+1 {{cannot be used with type 'bool'}} centroid bool invalid_bool() : VALUE { return false; } [shader("vertex")] -// expected-error@+1 {{cannot be used with type 'double'}} -sample double invalid_double() : VALUE { return 0; } -[shader("vertex")] // expected-error@+2 {{'nointerpolation' cannot be used on SV_Position}} // expected-note@+1 {{conflicting attribute is here}} nointerpolation float4 invalid_position() : SV_Position { return 0; } @@ -79,8 +76,6 @@ float4 valid_input(linear uint a : VALUE) : SV_Position { return 0; } nointerpolation uint valid_integer() : VALUE { return 0; } [shader("vertex")] nointerpolation bool valid_bool() : VALUE { return false; } -[shader("vertex")] -nointerpolation double valid_double() : VALUE { return 0; } // Inner modifiers replace the entire inherited set, including the return's // modifier. Neither the integer nor the position should inherit sample/flat. diff --git a/clang/test/SemaHLSL/interpolation-modifiers.hlsl b/clang/test/SemaHLSL/interpolation-modifiers.hlsl index b278eba8d26a7..7105a4ae6268d 100644 --- a/clang/test/SemaHLSL/interpolation-modifiers.hlsl +++ b/clang/test/SemaHLSL/interpolation-modifiers.hlsl @@ -83,12 +83,10 @@ float4 invalid_types( centroid uint2 b : B, // expected-error@+1 {{cannot be used with type 'bool'}} noperspective bool c : C, - // expected-error@+1 {{cannot be used with type 'double'}} - sample double d : D, // expected-error@+1 {{cannot be used with type 'int'}} center int e[2][3] : E, - // expected-error@+1 {{cannot be used with type 'double'}} - linear double2x2 f : F, + // expected-error@+1 {{cannot be used with type 'int'}} + linear int2x2 f : F, // expected-error@+1 {{cannot be used with type 'int'}} linear in int g : G) : SV_Target { return 0; @@ -96,8 +94,8 @@ float4 invalid_types( [shader("pixel")] float4 valid_types(nointerpolation int a : A, nointerpolation bool2 b : B, - nointerpolation double c : C, nointerpolation int d[2] : D, - nointerpolation double2x2 e : E, linear float f : F, + nointerpolation int d[2] : D, + nointerpolation int2x2 e : E, linear float f : F, centroid half2 g : G, sample float2x2 h : H) : SV_Target { return 0; } diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp index af8f79c052e3b..1ff2b32b50132 100644 --- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -83,10 +83,8 @@ TEST(HLSLInterpolationTest, ComponentDefaults) { EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, SemanticKind::Arbitrary, Stage, IO), InterpMode::Linear); - for (CompType Type : - {CompType::I1, CompType::I16, CompType::U16, CompType::I32, - CompType::U32, CompType::I64, CompType::U64, CompType::F64, - CompType::SNormF64, CompType::UNormF64}) + for (CompType Type : {CompType::I1, CompType::I16, CompType::U16, + CompType::I32, CompType::U32}) EXPECT_EQ(normalizeInterpolationMode(InterpMode::Undefined, Type, SemanticKind::Arbitrary, Stage, IO), InterpMode::Constant); >From 0db852a57b05fd90b119c364b238ecb0991bd936 Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 24 Sep 2026 17:08:31 +0000 Subject: [PATCH 09/10] review: use structured bindings --- llvm/unittests/Frontend/HLSLInterpolationTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp index 1ff2b32b50132..f875efda6219d 100644 --- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -61,15 +61,15 @@ TEST(HLSLInterpolationTest, SamplingLocations) { }; // Non-location keywords must not imply an explicit center modifier or change // sampling-location precedence, even in invalid interpolation combinations. - for (const auto &Case : Cases) + for (const auto &[CaseModifiers, Expected] : Cases) for (Mod Other : {Mod::None, Mod::NoInterpolation, Mod::Linear, Mod::NoPerspective, Mod::Linear | Mod::NoPerspective, Mod::NoInterpolation | Mod::Linear, Mod::NoInterpolation | Mod::NoPerspective, Mod::NoInterpolation | Mod::Linear | Mod::NoPerspective}) { - Mod Modifiers = Case.Modifiers | Other; + Mod Modifiers = CaseModifiers | Other; SCOPED_TRACE(static_cast<unsigned>(Modifiers)); - EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Case.Expected); + EXPECT_EQ(getInterpolationSamplingLocation(Modifiers), Expected); } } >From 958487f41c243d129311fa4157955ee9878dbb8a Mon Sep 17 00:00:00 2001 From: Finn Plummer <[email protected]> Date: Thu, 24 Sep 2026 17:09:16 +0000 Subject: [PATCH 10/10] review: remove hard-coded bounds --- llvm/unittests/Frontend/HLSLInterpolationTest.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp index f875efda6219d..d73de2cb8cb11 100644 --- a/llvm/unittests/Frontend/HLSLInterpolationTest.cpp +++ b/llvm/unittests/Frontend/HLSLInterpolationTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "llvm/ADT/STLExtras.h" #include "llvm/Frontend/HLSL/SemanticSignatures.h" #include "gtest/gtest.h" @@ -105,12 +106,12 @@ TEST(HLSLInterpolationTest, PositionAndExplicitModes) { for (auto Stage : {Triple::Pixel, Triple::Vertex}) { SCOPED_TRACE(Triple::getEnvironmentTypeName(Stage).str()); IOType IO = Stage == Triple::Pixel ? IOType::In : IOType::Out; - for (unsigned I = 0; I != 9; ++I) { + for (auto [I, Expected] : llvm::enumerate(PositionModes)) { SCOPED_TRACE(I); auto Mode = static_cast<InterpMode>(I); EXPECT_EQ(normalizeInterpolationMode(Mode, CompType::F32, SemanticKind::Position, Stage, IO), - PositionModes[I]); + Expected); if (Mode != InterpMode::Undefined) EXPECT_EQ(normalizeInterpolationMode( Mode, CompType::F32, SemanticKind::Arbitrary, Stage, IO), _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
