Skip to content

Conversation

@Keenuts
Copy link
Contributor

@Keenuts Keenuts commented Nov 6, 2025

Previously, we had 2 level of attributes:

  • HLSLUnparsedSemantic
  • N attributes, one for each known system semantic.

The first was assigned during parsing, and carried no other meaning than "there is a semantic token". It was then converted to one of the N attributes later during Sema.
Those attributes also carried informations like "is indexable" or "is index explicit".

This had a few issues:

  • there was no difference between a semantic attribute applied to a decl, and the effective semantic in the entrypoint use context.
  • having the indexable bit was not useful.
  • semantic constraints checks were split between .td files and sema.

Also, existing implementation had effective attributes attached to the type decl or parameters, meaning struct decl reuse across entrypoints of in a nested type was not supported, even if legal in HLSL.

This PR tried to simplifies semantic attribute by having 3 attributes:

  • HLSLUnpasedSemantic
  • HLSLParsedSemantic
  • HLSLAppliedSemantic

Initial parsing emits an HLSLUnparsedSemantic. We simply say "here is an HLSL semantic token", but we don't do any semantic check.

Then, Sema does initial validation and transforms an UnparseSemantic into a ParsedSemantic. This validates a system semantic is known, or that the associated type is valid (like uint3 for a ThreadIndex).

Then, once we parse an actual shader entrypoint, we can know how semantics are used in a real context. This step emits a list of AppliedSemantic. Those are the actual semantic in use for this specific entrypoint.
Those attributes are attached to each entrypoint parameter, as a flat list matching the semantic structure flattening HLSL defines. At this stage of sema, index collision or other stage compabitility checkes are carried.

This allows codegen to simply iterate over this list and emit the proper DXIL or SPIR-V codegen.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. HLSL HLSL Language Support labels Nov 6, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 6, 2025

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-clang-codegen

Author: Nathan Gauër (Keenuts)

Changes

Previously, we had 2 level of attributes:

  • HLSLUnparsedSemantic
  • N attributes, one for each known system semantic.

The first was assigned during parsing, and carried no other meaning than "there is a semantic token". It was then converted to one of the N attributes later during Sema.
Those attributes also carried informations like "is indexable" or "is index explicit".

This had a few issues:

  • there was no difference between a semantic attribute applied to a decl, and the effective semantic in the entrypoint use context.
  • having the indexable bit was not useful.
  • semantic constraints checks were split between .td files and sema.

Also, existing implementation had effective attributes attached to the type decl or parameters, meaning struct decl reuse across entrypoints of in a nested type was not supported, even if legal in HLSL.

This PR tried to simplifies semantic attribute by having 3 attributes:

  • HLSLUnpasedSemantic
  • HLSLParsedSemantic
  • HLSLAppliedSemantic

Initial parsing emits an HLSLUnparsedSemantic. We simply say "here is an HLSL semantic token", but we don't do any semantic check.

Then, Sema does initial validation and transforms an UnparseSemantic into a ParsedSemantic. This validates a system semantic is known, or that the associated type is valid (like uint3 for a ThreadIndex).

Then, once we parse an actual shader entrypoint, we can know how semantics are used in a real context. This step emits a list of AppliedSemantic. Those are the actual semantic in use for this specific entrypoint.
Those attributes are attached to each entrypoint parameter, as a flat list matching the semantic structure flattening HLSL defines. At this stage of sema, index collision or other stage compabitility checkes are carried.

This allows codegen to simply iterate over this list and emit the proper DXIL or SPIR-V codegen.


Patch is 55.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/166796.diff

20 Files Affected:

  • (modified) clang/include/clang/AST/Attr.h (+7-32)
  • (modified) clang/include/clang/Basic/Attr.td (+17-27)
  • (modified) clang/include/clang/Basic/AttrDocs.td (-61)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+11-18)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+41-44)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+16-12)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+80-83)
  • (modified) clang/test/CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl (+1-1)
  • (modified) clang/test/CodeGenHLSL/semantics/SV_GroupID-noindex.hlsl (+6-1)
  • (modified) clang/test/CodeGenHLSL/semantics/SV_GroupThreadID-noindex.hlsl (+1-1)
  • (modified) clang/test/CodeGenHLSL/semantics/semantic.array.hlsl (+5-5)
  • (modified) clang/test/ParserHLSL/hlsl_annotations_on_struct_members.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/Semantics/entry_parameter.hlsl (+8-9)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.hlsl (+2-3)
  • (modified) clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl (+4-5)
  • (added) clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl (+26)
  • (modified) clang/test/SemaHLSL/Semantics/semantics-valid.hlsl (+26-25)
  • (modified) clang/test/SemaHLSL/Semantics/valid_entry_parameter.hlsl (+15-15)
  • (modified) clang/test/TableGen/HLSLAttribute-errors.td (+1-1)
  • (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+1-17)
diff --git a/clang/include/clang/AST/Attr.h b/clang/include/clang/AST/Attr.h
index 14d7caa0e16d7..e36184f232f8a 100644
--- a/clang/include/clang/AST/Attr.h
+++ b/clang/include/clang/AST/Attr.h
@@ -233,44 +233,19 @@ class HLSLAnnotationAttr : public InheritableAttr {
   }
 };
 
-class HLSLSemanticAttr : public HLSLAnnotationAttr {
-  unsigned SemanticIndex = 0;
-  LLVM_PREFERRED_TYPE(bool)
-  unsigned SemanticIndexable : 1;
-  LLVM_PREFERRED_TYPE(bool)
-  unsigned SemanticExplicitIndex : 1;
-
-  Decl *TargetDecl = nullptr;
-
+class HLSLSemanticBaseAttr : public HLSLAnnotationAttr {
 protected:
-  HLSLSemanticAttr(ASTContext &Context, const AttributeCommonInfo &CommonInfo,
-                   attr::Kind AK, bool IsLateParsed,
-                   bool InheritEvenIfAlreadyPresent, bool SemanticIndexable)
+  HLSLSemanticBaseAttr(ASTContext &Context,
+                       const AttributeCommonInfo &CommonInfo, attr::Kind AK,
+                       bool IsLateParsed, bool InheritEvenIfAlreadyPresent)
       : HLSLAnnotationAttr(Context, CommonInfo, AK, IsLateParsed,
-                           InheritEvenIfAlreadyPresent) {
-    this->SemanticIndexable = SemanticIndexable;
-    this->SemanticExplicitIndex = false;
-  }
+                           InheritEvenIfAlreadyPresent) {}
 
 public:
-  bool isSemanticIndexable() const { return SemanticIndexable; }
-
-  void setSemanticIndex(unsigned SemanticIndex) {
-    this->SemanticIndex = SemanticIndex;
-    this->SemanticExplicitIndex = true;
-  }
-
-  unsigned getSemanticIndex() const { return SemanticIndex; }
-
-  bool isSemanticIndexExplicit() const { return SemanticExplicitIndex; }
-
-  void setTargetDecl(Decl *D) { TargetDecl = D; }
-  Decl *getTargetDecl() const { return TargetDecl; }
-
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Attr *A) {
-    return A->getKind() >= attr::FirstHLSLSemanticAttr &&
-           A->getKind() <= attr::LastHLSLSemanticAttr;
+    return A->getKind() >= attr::FirstHLSLSemanticBaseAttr &&
+           A->getKind() <= attr::LastHLSLSemanticBaseAttr;
   }
 };
 
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 1013bfc575747..9081897bbf147 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -779,18 +779,6 @@ class DeclOrStmtAttr : InheritableAttr;
 /// An attribute class for HLSL Annotations.
 class HLSLAnnotationAttr : InheritableAttr;
 
-class HLSLSemanticAttr<bit Indexable> : HLSLAnnotationAttr {
-  bit SemanticIndexable = Indexable;
-  int SemanticIndex = 0;
-  bit SemanticExplicitIndex = 0;
-
-  let Spellings = [];
-  let Subjects = SubjectList<[ParmVar, Field, Function]>;
-  let LangOpts = [HLSL];
-  let Args = [DeclArgument<Named, "Target">, IntArgument<"SemanticIndex">,
-              BoolArgument<"SemanticExplicitIndex">];
-}
-
 /// A target-specific attribute.  This class is meant to be used as a mixin
 /// with InheritableAttr or Attr depending on the attribute's needs.
 class TargetSpecificAttr<TargetSpec target> {
@@ -5017,28 +5005,30 @@ def HLSLUnparsedSemantic : HLSLAnnotationAttr {
   let Documentation = [InternalOnly];
 }
 
-def HLSLUserSemantic : HLSLSemanticAttr</* Indexable= */ 1> {
-  let Documentation = [InternalOnly];
-}
+class HLSLSemanticBaseAttr : HLSLAnnotationAttr {
+  int SemanticIndex = 0;
 
-def HLSLSV_Position : HLSLSemanticAttr</* Indexable= */ 1> {
-  let Documentation = [HLSLSV_PositionDocs];
+  let Spellings = [];
+  let Subjects = SubjectList<[ParmVar, Field, Function]>;
+  let LangOpts = [HLSL];
 }
 
-def HLSLSV_GroupThreadID : HLSLSemanticAttr</* Indexable= */ 0> {
-  let Documentation = [HLSLSV_GroupThreadIDDocs];
-}
+def HLSLParsedSemantic : HLSLSemanticBaseAttr {
+  let Spellings = [];
+  let Subjects = SubjectList<[ParmVar, Field, Function]>;
+  let LangOpts = [HLSL];
+  let Documentation = [InternalOnly];
 
-def HLSLSV_GroupID : HLSLSemanticAttr</* Indexable= */ 0> {
-  let Documentation = [HLSLSV_GroupIDDocs];
+  let Args = [StringArgument<"SemanticName">, IntArgument<"SemanticIndex">];
 }
 
-def HLSLSV_GroupIndex : HLSLSemanticAttr</* Indexable= */ 0> {
-  let Documentation = [HLSLSV_GroupIndexDocs];
-}
+def HLSLAppliedSemantic : HLSLSemanticBaseAttr {
+  let Spellings = [];
+  let Subjects = SubjectList<[ParmVar, Field, Function]>;
+  let LangOpts = [HLSL];
+  let Documentation = [InternalOnly];
 
-def HLSLSV_DispatchThreadID : HLSLSemanticAttr</* Indexable= */ 0> {
-  let Documentation = [HLSLSV_DispatchThreadIDDocs];
+  let Args = [StringArgument<"SemanticName">, IntArgument<"SemanticIndex">];
 }
 
 def HLSLPackOffset: HLSLAnnotationAttr {
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 1be9a96aa44de..e064e69ce6ece 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8672,38 +8672,6 @@ randomized.
   }];
 }
 
-def HLSLSV_GroupThreadIDDocs : Documentation {
-  let Category = DocHLSLSemantics;
-  let Content = [{
-The ``SV_GroupThreadID`` semantic, when applied to an input parameter, specifies which
-individual thread within a thread group is executing in. This attribute is
-only supported in compute shaders.
-
-The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-groupthreadid
-  }];
-}
-
-def HLSLSV_GroupIDDocs : Documentation {
-  let Category = DocHLSLSemantics;
-  let Content = [{
-The ``SV_GroupID`` semantic, when applied to an input parameter, specifies which
-thread group a shader is executing in. This attribute is only supported in compute shaders.
-
-The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-groupid
-  }];
-}
-
-def HLSLSV_GroupIndexDocs : Documentation {
-  let Category = DocHLSLSemantics;
-  let Content = [{
-The ``SV_GroupIndex`` semantic, when applied to an input parameter, specifies a
-data binding to map the group index to the specified parameter. This attribute
-is only supported in compute shaders.
-
-The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-groupindex
-  }];
-}
-
 def HLSLResourceBindingDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
@@ -8750,35 +8718,6 @@ The full documentation is available here: https://learn.microsoft.com/en-us/wind
   }];
 }
 
-def HLSLSV_DispatchThreadIDDocs : Documentation {
-  let Category = DocHLSLSemantics;
-  let Content = [{
-The ``SV_DispatchThreadID`` semantic, when applied to an input parameter,
-specifies a data binding to map the global thread offset within the Dispatch
-call (per dimension of the group) to the specified parameter.
-When applied to a field of a struct, the data binding is specified to the field
-when the struct is used as a parameter type.
-The semantic on the field is ignored when not used as a parameter.
-This attribute is only supported in compute shaders.
-
-The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-dispatchthreadid
-  }];
-}
-
-def HLSLSV_PositionDocs : Documentation {
-  let Category = DocHLSLSemantics;
-  let Content = [{
-The ``SV_Position`` semantic, when applied to an input parameter in a pixel
-shader, contains the location of the pixel center (x, y) in screen space.
-This semantic can be applied to the parameter, or a field in a struct used
-as an input parameter.
-This attribute is supported as an input in pixel, hull, domain and mesh shaders.
-This attribute is supported as an output in vertex, geometry and domain shaders.
-
-The full documentation is available here: https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics
-  }];
-}
-
 def HLSLGroupSharedAddressSpaceDocs : Documentation {
   let Category = DocCatVariable;
   let Content = [{
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 28b03ac4c4676..86da323892f98 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -178,18 +178,11 @@ class SemaHLSL : public SemaBase {
   bool handleResourceTypeAttr(QualType T, const ParsedAttr &AL);
 
   template <typename T>
-  T *createSemanticAttr(const AttributeCommonInfo &ACI, NamedDecl *TargetDecl,
+  T *createSemanticAttr(const AttributeCommonInfo &ACI,
                         std::optional<unsigned> Location) {
-    T *Attr =
-        ::new (getASTContext()) T(getASTContext(), ACI, TargetDecl,
-                                  Location.value_or(0), Location.has_value());
-
-    if (!Attr->isSemanticIndexable() && Location.has_value()) {
-      Diag(Attr->getLocation(), diag::err_hlsl_semantic_indexing_not_supported)
-          << Attr->getAttrName()->getName();
-      return nullptr;
-    }
-    return Attr;
+    return ::new (getASTContext())
+        T(getASTContext(), ACI, ACI.getAttrName()->getName(),
+          Location.value_or(0));
   }
 
   void diagnoseSystemSemanticAttr(Decl *D, const ParsedAttr &AL,
@@ -247,7 +240,7 @@ class SemaHLSL : public SemaBase {
   IdentifierInfo *RootSigOverrideIdent = nullptr;
 
   struct SemanticInfo {
-    HLSLSemanticAttr *Semantic;
+    HLSLParsedSemanticAttr *Semantic;
     std::optional<uint32_t> Index;
   };
 
@@ -257,14 +250,14 @@ class SemaHLSL : public SemaBase {
                                                const RecordType *RT);
 
   void checkSemanticAnnotation(FunctionDecl *EntryPoint, const Decl *Param,
-                               const HLSLSemanticAttr *SemanticAttr);
-  HLSLSemanticAttr *createSemantic(const SemanticInfo &Semantic,
-                                   DeclaratorDecl *TargetDecl);
-  bool determineActiveSemanticOnScalar(FunctionDecl *FD, DeclaratorDecl *D,
+                               const HLSLAppliedSemanticAttr *SemanticAttr);
+  bool determineActiveSemanticOnScalar(FunctionDecl *FD,
+                                       DeclaratorDecl *OutputDecl,
+                                       DeclaratorDecl *D,
                                        SemanticInfo &ActiveSemantic,
                                        llvm::StringSet<> &ActiveInputSemantics);
-  bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *D,
-                               SemanticInfo &ActiveSemantic,
+  bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *OutputDecl,
+                               DeclaratorDecl *D, SemanticInfo &ActiveSemantic,
                                llvm::StringSet<> &ActiveInputSemantics);
 
   void processExplicitBindingsOnDecl(VarDecl *D);
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index e392a12044a39..d40ba2539cd00 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -587,7 +587,7 @@ static llvm::Value *createSPIRVLocationLoad(IRBuilder<> &B, llvm::Module &M,
 
 llvm::Value *
 CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
-                                         HLSLSemanticAttr *Semantic,
+                                         HLSLAppliedSemanticAttr *Semantic,
                                          std::optional<unsigned> Index) {
   Twine BaseName = Twine(Semantic->getAttrName()->getName());
   Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
@@ -605,7 +605,7 @@ CGHLSLRuntime::emitSPIRVUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
 
 llvm::Value *
 CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
-                                        HLSLSemanticAttr *Semantic,
+                                        HLSLAppliedSemanticAttr *Semantic,
                                         std::optional<unsigned> Index) {
   Twine BaseName = Twine(Semantic->getAttrName()->getName());
   Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
@@ -625,7 +625,7 @@ CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
 
 llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
-    HLSLSemanticAttr *Semantic, std::optional<unsigned> Index) {
+    HLSLAppliedSemanticAttr *Semantic, std::optional<unsigned> Index) {
   if (CGM.getTarget().getTriple().isSPIRV())
     return emitSPIRVUserSemanticLoad(B, Type, Semantic, Index);
 
@@ -637,14 +637,16 @@ llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
 
 llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
-    Attr *Semantic, std::optional<unsigned> Index) {
-  if (isa<HLSLSV_GroupIndexAttr>(Semantic)) {
+    HLSLSemanticBaseAttr *Semantic, std::optional<unsigned> Index) {
+
+  std::string SemanticName = Semantic->getAttrName()->getName().upper();
+  if (SemanticName == "SV_GROUPINDEX") {
     llvm::Function *GroupIndex =
         CGM.getIntrinsic(getFlattenedThreadIdInGroupIntrinsic());
     return B.CreateCall(FunctionCallee(GroupIndex));
   }
 
-  if (isa<HLSLSV_DispatchThreadIDAttr>(Semantic)) {
+  if (SemanticName == "SV_DISPATCHTHREADID") {
     llvm::Intrinsic::ID IntrinID = getThreadIdIntrinsic();
     llvm::Function *ThreadIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -653,7 +655,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     return buildVectorInput(B, ThreadIDIntrinsic, Type);
   }
 
-  if (isa<HLSLSV_GroupThreadIDAttr>(Semantic)) {
+  if (SemanticName == "SV_GROUPTHREADID") {
     llvm::Intrinsic::ID IntrinID = getGroupThreadIdIntrinsic();
     llvm::Function *GroupThreadIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -662,7 +664,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     return buildVectorInput(B, GroupThreadIDIntrinsic, Type);
   }
 
-  if (isa<HLSLSV_GroupIDAttr>(Semantic)) {
+  if (SemanticName == "SV_GROUPID") {
     llvm::Intrinsic::ID IntrinID = getGroupIdIntrinsic();
     llvm::Function *GroupIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -671,44 +673,31 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
     return buildVectorInput(B, GroupIDIntrinsic, Type);
   }
 
-  if (HLSLSV_PositionAttr *S = dyn_cast<HLSLSV_PositionAttr>(Semantic)) {
+  if (SemanticName == "SV_POSITION") {
     if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel)
       return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
-                                    S->getAttrName()->getName(),
+                                    Semantic->getAttrName()->getName(),
                                     /* BuiltIn::FragCoord */ 15);
   }
 
   llvm_unreachable("non-handled system semantic. FIXME.");
 }
 
-llvm::Value *
-CGHLSLRuntime::handleScalarSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD,
-                                        llvm::Type *Type,
-                                        const clang::DeclaratorDecl *Decl) {
-
-  HLSLSemanticAttr *Semantic = nullptr;
-  for (HLSLSemanticAttr *Item : FD->specific_attrs<HLSLSemanticAttr>()) {
-    if (Item->getTargetDecl() == Decl) {
-      Semantic = Item;
-      break;
-    }
-  }
-  // Sema must create one attribute per scalar field.
-  assert(Semantic);
-
-  std::optional<unsigned> Index = std::nullopt;
-  if (Semantic->isSemanticIndexExplicit())
-    Index = Semantic->getSemanticIndex();
+llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
+    const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
 
-  if (isa<HLSLUserSemanticAttr>(Semantic))
-    return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
-  return emitSystemSemanticLoad(B, Type, Decl, Semantic, Index);
+  std::optional<unsigned> Index = Semantic->getSemanticIndex();
+  if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
+    return emitSystemSemanticLoad(B, Type, Decl, Semantic, Index);
+  return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
 }
 
-llvm::Value *
-CGHLSLRuntime::handleStructSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD,
-                                        llvm::Type *Type,
-                                        const clang::DeclaratorDecl *Decl) {
+llvm::Value *CGHLSLRuntime::handleStructSemanticLoad(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> &begin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> end) {
   const llvm::StructType *ST = cast<StructType>(Type);
   const clang::RecordDecl *RD = Decl->getType()->getAsRecordDecl();
 
@@ -718,8 +707,8 @@ CGHLSLRuntime::handleStructSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD,
   llvm::Value *Aggregate = llvm::PoisonValue::get(Type);
   auto FieldDecl = RD->field_begin();
   for (unsigned I = 0; I < ST->getNumElements(); ++I) {
-    llvm::Value *ChildValue =
-        handleSemanticLoad(B, FD, ST->getElementType(I), *FieldDecl);
+    llvm::Value *ChildValue = handleSemanticLoad(B, FD, ST->getElementType(I),
+                                                 *FieldDecl, begin, end);
     assert(ChildValue);
     Aggregate = B.CreateInsertValue(Aggregate, ChildValue, I);
     ++FieldDecl;
@@ -728,13 +717,18 @@ CGHLSLRuntime::handleStructSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD,
   return Aggregate;
 }
 
-llvm::Value *
-CGHLSLRuntime::handleSemanticLoad(IRBuilder<> &B, const FunctionDecl *FD,
-                                  llvm::Type *Type,
-                                  const clang::DeclaratorDecl *Decl) {
+llvm::Value *CGHLSLRuntime::handleSemanticLoad(
+    IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
+    const clang::DeclaratorDecl *Decl,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> &begin,
+    specific_attr_iterator<HLSLAppliedSemanticAttr> end) {
+  assert(end != begin);
   if (Type->isStructTy())
-    return handleStructSemanticLoad(B, FD, Type, Decl);
-  return handleScalarSemanticLoad(B, FD, Type, Decl);
+    return handleStructSemanticLoad(B, FD, Type, Decl, begin, end);
+
+  HLSLAppliedSemanticAttr *Attr = *begin;
+  ++begin;
+  return handleScalarSemanticLoad(B, FD, Type, Decl, Attr);
 }
 
 void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
@@ -781,6 +775,8 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
     }
 
     const ParmVarDecl *PD = FD->getParamDecl(Param.getArgNo() - SRetOffset);
+    auto AttrBegin = PD->specific_attr_begin<HLSLAppliedSemanticAttr>();
+    auto AttrEnd = PD->specific_attr_end<HLSLAppliedSemanticAttr>();
     llvm::Value *SemanticValue = nullptr;
     if ([[maybe_unused]] HLSLParamModifierAttr *MA =
             PD->getAttr<HLSLParamModifierAttr>()) {
@@ -788,7 +784,8 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
     } else {
       llvm::Type *ParamType =
           Param.hasByValAttr() ? Param.getParamByValType() : Param.getType();
-      SemanticValue = handleSemanticLoad(B, FD, ParamType, PD);
+      SemanticValue =
+          handleSemanticLoad(B, FD, ParamType, PD, AttrBegin, AttrEnd);
       if (!SemanticValue)
         return;
       if (Param.hasByValAttr()) {
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 9d31714ab8606..1c844f8c218ef 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -146,22 +146,26 @@ class CGHLSLRuntime {
 
   llvm::Value *emitSystemSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
                                       const clang::DeclaratorDecl *Decl,
-                                      Attr *Semantic,
+                                      HLSLSemanticBaseAttr *Semantic,
                                       std::optional<unsigned> Index);
 
   llvm::Value *handleScalarSemanticLoad(llvm::IRBuilder<> &B,
                                         const FunctionDecl *FD,
                                         llvm::Type *Type,
-                                        const clang::DeclaratorDecl *Decl);
+                                        const clang::DeclaratorDecl *Decl,
+                                        HLSLAppliedSemanticAttr *Semantic);
 
-  llvm::Value *handleStructSemanticLoad(llvm::IRBuilder<> &B,
-                                        const FunctionDecl *FD,
-                                        llvm::Type *Type,
-                      ...
[truncated]

Previously, we had 2 level of attributes:
 - HLSLUnparsedSemantic
 - N attributes, one for each known system semantic.

The first was assigned during parsing, and carried no other meaning than
"there is a semantic token". It was then converted to one of the N
attributes later during Sema.
Those attributes also carried informations like "is indexable" or "is
index explicit".

This had a few issues:
 - there was no difference between a semantic attribute applied to a
   decl, and the effective semantic in the entrypoint use context.
 - having the indexable bit was not useful.
 - semantic constraints checks were split between .td files and sema.

Also, existing implementation had effective attributes attached to the
type decl or parameters, meaning struct decl reuse across entrypoints of
in a nested type was not supported, even if legal in HLSL.

This PR tried to simplifies semantic attribute by having 3 attributes:

 - HLSLUnpasedSemantic
 - HLSLParsedSemantic
 - HLSLAppliedSemantic

Initial parsing emits an `HLSLUnparsedSemantic`. We simply say "here is
an HLSL semantic token", but we don't do any semantic check.

Then, Sema does initial validation and transforms an UnparseSemantic
into a ParsedSemantic. This validates a system semantic is known, or
that the associated type is valid (like uint3 for a ThreadIndex).

Then, once we parse an actual shader entrypoint, we can know how
semantics are used in a real context. This step emits a list of
AppliedSemantic. Those are the actual semantic in use for this specific
entrypoint.
Those attributes are attached to each entrypoint parameter, as a flat
list matching the semantic structure flattening HLSL defines. At this
stage of sema, index collision or other stage compabitility checkes are
carried.

This allows codegen to simply iterate over this list and emit the proper
DXIL or SPIR-V codegen.
@Keenuts Keenuts force-pushed the hlsl-attr-refactoring branch from 296b978 to 0afad1c Compare November 7, 2025 10:19
Copy link
Contributor

@s-perron s-perron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor style comments.

@Keenuts
Copy link
Contributor Author

Keenuts commented Nov 12, 2025

Thanks! Applied all changes PTAL
@tex3d any objections?

@Keenuts Keenuts requested a review from s-perron November 12, 2025 13:55
Copy link
Contributor

@tex3d tex3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a great step in the right direction, thanks!

@Keenuts Keenuts merged commit dba8507 into llvm:main Nov 12, 2025
12 checks passed
@Keenuts Keenuts deleted the hlsl-attr-refactoring branch November 12, 2025 17:54
@Kewen12
Copy link
Contributor

Kewen12 commented Nov 12, 2025

Hi! We saw test failure in our bot that seems related to this PR. Could you please take a look? Thanks!

bot: https://lab.llvm.org/buildbot/#/builders/10/builds/17187

Clang::DispatchThreadID-noindex.hlsl
Clang::SV_GroupThreadID-noindex.hlsl
Clang::SV_GroupID-noindex.hlsl

@llvm-ci
Copy link
Collaborator

llvm-ci commented Nov 12, 2025

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-sie-ubuntu-fast running on sie-linux-worker while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/144/builds/39988

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang :: CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl' FAILED ********************
Exit Code: -6

Command Output (stdout):
--
# RUN: at line 1
/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/22/include -nostdsysteminc -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl -verify -verify-ignore-unexpected=note,error
# executed command: /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/22/include -nostdsysteminc -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl -verify -verify-ignore-unexpected=note,error
# .---command stderr------------
# | LLVM ERROR: out of memory
# | Allocation failed
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace, preprocessed source, and associated run script.
# | Stack dump:
# | 0.	Program arguments: /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/22/include -nostdsysteminc -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl -verify -verify-ignore-unexpected=note,error
# | 1.	/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/semantics/DispatchThreadID-noindex.hlsl:6:43: current parser token '{'
# |  #0 0x0000653cf0096830 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x224e830)
# |  #1 0x0000653cf00934ef llvm::sys::RunSignalHandlers() (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x224b4ef)
# |  #2 0x0000653cf0093642 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
# |  #3 0x0000705546a63520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
# |  #4 0x0000705546ab79fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
# |  #5 0x0000705546ab79fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
# |  #6 0x0000705546ab79fc pthread_kill ./nptl/pthread_kill.c:89:10
# |  #7 0x0000705546a63476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
# |  #8 0x0000705546a497f3 abort ./stdlib/abort.c:81:7
# |  #9 0x0000653ceffe79d9 (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x219f9d9)
# | #10 0x0000653cf001191b llvm::SmallVectorBase<unsigned long>::grow_pod(void*, unsigned long, unsigned long) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x21c991b)
# | #11 0x0000653cf00701c9 llvm::raw_svector_ostream::write_impl(char const*, unsigned long) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x22281c9)
# | #12 0x0000653cf007161a llvm::raw_ostream::write(char const*, unsigned long) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x222961a)
# | #13 0x0000653cf003745d llvm::Twine::printOneChild(llvm::raw_ostream&, llvm::Twine::Child, llvm::Twine::NodeKind) const (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x21ef45d)
# | #14 0x0000653cf003778f llvm::Twine::str[abi:cxx11]() const (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x21ef78f)
# | #15 0x0000653cf2c5cabe clang::SemaHLSL::checkSemanticAnnotation(clang::FunctionDecl*, clang::Decl const*, clang::HLSLAppliedSemanticAttr const*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4e14abe)
# | #16 0x0000653cf2c5ce18 clang::SemaHLSL::determineActiveSemanticOnScalar(clang::FunctionDecl*, clang::DeclaratorDecl*, clang::DeclaratorDecl*, clang::SemaHLSL::SemanticInfo&, llvm::StringSet<llvm::MallocAllocator>&) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4e14e18)
# | #17 0x0000653cf2c5f830 clang::SemaHLSL::CheckEntryPoint(clang::FunctionDecl*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4e17830)
# | #18 0x0000653cf29b0bed clang::Sema::ActOnFunctionDeclarator(clang::Scope*, clang::Declarator&, clang::DeclContext*, clang::TypeSourceInfo*, clang::LookupResult&, llvm::MutableArrayRef<clang::TemplateParameterList*>, bool&) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4b68bed)
# | #19 0x0000653cf29b33f4 clang::Sema::HandleDeclarator(clang::Scope*, clang::Declarator&, llvm::MutableArrayRef<clang::TemplateParameterList*>) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4b6b3f4)
# | #20 0x0000653cf29b418d clang::Sema::ActOnStartOfFunctionDef(clang::Scope*, clang::Declarator&, llvm::MutableArrayRef<clang::TemplateParameterList*>, clang::SkipBodyInfo*, clang::Sema::FnBodyKind) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4b6c18d)
# | #21 0x0000653cf25bfb25 clang::Parser::ParseFunctionDefinition(clang::ParsingDeclarator&, clang::Parser::ParsedTemplateInfo const&, clang::Parser::LateParsedAttrList*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4777b25)
# | #22 0x0000653cf260486d clang::Parser::ParseDeclGroup(clang::ParsingDeclSpec&, clang::DeclaratorContext, clang::ParsedAttributes&, clang::Parser::ParsedTemplateInfo&, clang::SourceLocation*, clang::Parser::ForRangeInit*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x47bc86d)
# | #23 0x0000653cf25ba5ac clang::Parser::ParseDeclOrFunctionDefInternal(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec&, clang::AccessSpecifier) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x47725ac)
# | #24 0x0000653cf25bad0f clang::Parser::ParseDeclarationOrFunctionDefinition(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*, clang::AccessSpecifier) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x4772d0f)
# | #25 0x0000653cf25c4886 clang::Parser::ParseExternalDeclaration(clang::ParsedAttributes&, clang::ParsedAttributes&, clang::ParsingDeclSpec*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x477c886)
# | #26 0x0000653cf25c58f5 clang::Parser::ParseTopLevelDecl(clang::OpaquePtr<clang::DeclGroupRef>&, clang::Sema::ModuleImportState&) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x477d8f5)
# | #27 0x0000653cf25a26ca clang::ParseAST(clang::Sema&, bool, bool) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x475a6ca)
# | #28 0x0000653cf0d0af3e clang::HLSLFrontendAction::ExecuteAction() (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x2ec2f3e)
# | #29 0x0000653cf0cf4689 clang::FrontendAction::Execute() (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x2eac689)
# | #30 0x0000653cf0c74275 clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x2e2c275)
# | #31 0x0000653cf0de9ac3 clang::ExecuteCompilerInvocation(clang::CompilerInstance*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0x2fa1ac3)
# | #32 0x0000653ceea839f7 cc1_main(llvm::ArrayRef<char const*>, char const*, void*) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0xc3b9f7)
# | #33 0x0000653ceea7a14a ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) driver.cpp:0:0
# | #34 0x0000653ceea7e428 clang_main(int, char**, llvm::ToolContext const&) (/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang+0xc36428)
...

@jplehr
Copy link
Contributor

jplehr commented Nov 12, 2025

Hi, what's the status on this? If there is a bit more time needed to investigate, let's revert in the meantime.

@Keenuts
Copy link
Contributor Author

Keenuts commented Nov 12, 2025

Hello!
Looks like a tentative fix has been merged already (thank you!)
cc54ee8
But I still see failure on the mainline. I'll revert this and look at it tomorrow (late in my country)

Keenuts added a commit to Keenuts/llvm-project that referenced this pull request Nov 12, 2025
Keenuts added a commit that referenced this pull request Nov 12, 2025
Reverting 2 commits from the mainline. The origin of the issue, and the
tentative fix-forward.
git-crd pushed a commit to git-crd/crd-llvm-project that referenced this pull request Nov 13, 2025
Previously, we had 2 level of attributes:
 - HLSLUnparsedSemantic
 - N attributes, one for each known system semantic.

The first was assigned during parsing, and carried no other meaning than
"there is a semantic token". It was then converted to one of the N
attributes later during Sema.
Those attributes also carried informations like "is indexable" or "is
index explicit".

This had a few issues:
- there was no difference between a semantic attribute applied to a
decl, and the effective semantic in the entrypoint use context.
 - having the indexable bit was not useful.
 - semantic constraints checks were split between .td files and sema.

Also, existing implementation had effective attributes attached to the
type decl or parameters, meaning struct decl reuse across entrypoints of
in a nested type was not supported, even if legal in HLSL.

This PR tried to simplifies semantic attribute by having 3 attributes:

 - HLSLUnpasedSemantic
 - HLSLParsedSemantic
 - HLSLAppliedSemantic

Initial parsing emits an `HLSLUnparsedSemantic`. We simply say "here is
an HLSL semantic token", but we don't do any semantic check.

Then, Sema does initial validation and transforms an UnparseSemantic
into a ParsedSemantic. This validates a system semantic is known, or
that the associated type is valid (like uint3 for a ThreadIndex).

Then, once we parse an actual shader entrypoint, we can know how
semantics are used in a real context. This step emits a list of
AppliedSemantic. Those are the actual semantic in use for this specific
entrypoint.
Those attributes are attached to each entrypoint parameter, as a flat
list matching the semantic structure flattening HLSL defines. At this
stage of sema, index collision or other stage compabitility checkes are
carried.

This allows codegen to simply iterate over this list and emit the proper
DXIL or SPIR-V codegen.
git-crd pushed a commit to git-crd/crd-llvm-project that referenced this pull request Nov 13, 2025
…lvm#167759)

Reverting 2 commits from the mainline. The origin of the issue, and the
tentative fix-forward.
Keenuts added a commit to Keenuts/llvm-project that referenced this pull request Nov 13, 2025
Re-land of llvm#166796 following CI failures.

This reverts commit 1d2429b.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants