Skip to content

Implement resource binding type prefix mismatch diagnostic infrastructure #97103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 44 commits into from
Aug 23, 2024

Conversation

bob80905
Copy link
Contributor

@bob80905 bob80905 commented Jun 28, 2024

There are currently no diagnostics being emitted for when a resource is bound to a register with an incorrect binding type prefix. For example, a CBuffer type resource should be bound with a a binding type prefix of 'b', but if instead the prefix is 'u', no errors will be emitted. This PR implements such diagnostics. The focus of this PR is to implement both the flag setting and diagnostic emisison steps specified in the relevant spec: microsoft/hlsl-specs#230
The relevant issue is: #57886
This is a continuation / refresh of this PR: #87578

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels Jun 28, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 28, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-hlsl

Author: Joshua Batista (bob80905)

Changes

There are currently no diagnostics being emitted for when a resource is bound to a register with an incorrect binding type prefix. For example, a CBuffer type resource should be bound with a a binding type prefix of 'b', but if instead the prefix is 'u', no errors will be emitted. This PR implements a set of diagnostics for when the prefix is not expected.
Fixes #57886
This is a continuation / refresh of this PR: #87578


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

12 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+1-1)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4-1)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+2)
  • (modified) clang/lib/Sema/HLSLExternalSemaSource.cpp (+14-12)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+228-4)
  • (modified) clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl (+2)
  • (modified) clang/test/AST/HLSL/cbuffer_tbuffer.hlsl (+4-2)
  • (modified) clang/test/AST/HLSL/packoffset.hlsl (+1)
  • (modified) clang/test/AST/HLSL/pch_hlsl_buffer.hlsl (+2)
  • (modified) clang/test/AST/HLSL/resource_binding_attr.hlsl (+4-2)
  • (modified) clang/test/SemaHLSL/resource_binding_attr_error.hlsl (+10-11)
  • (added) clang/test/SemaHLSL/resource_binding_attr_error_mismatch.hlsl (+74)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 452cd1810f653..c3d67e19656da 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4467,7 +4467,7 @@ def HLSLSV_GroupIndex: HLSLAnnotationAttr {
 
 def HLSLResourceBinding: InheritableAttr {
   let Spellings = [HLSLAnnotation<"register">];
-  let Subjects = SubjectList<[HLSLBufferObj, ExternalGlobalVar]>;
+  let Subjects = SubjectList<[HLSLBufferObj, ExternalGlobalVar], ErrorDiag>;
   let LangOpts = [HLSL];
   let Args = [StringArgument<"Slot">, StringArgument<"Space", 1>];
   let Documentation = [HLSLResourceBindingDocs];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 96f0c0f0205c2..3bf15ff5f2657 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12303,7 +12303,10 @@ def err_hlsl_missing_semantic_annotation : Error<
 def err_hlsl_init_priority_unsupported : Error<
   "initializer priorities are not supported in HLSL">;
 
-def err_hlsl_unsupported_register_type : Error<"invalid resource class specifier '%0' used; expected 'b', 's', 't', or 'u'">;
+def err_hlsl_mismatching_register_resource_type_and_name: Error<"invalid register name prefix '%0' for register resource type '%1' (expected %select{'t'|'u'|'b'|'s'}2)">;
+def err_hlsl_mismatching_register_builtin_type_and_name: Error<"invalid register name prefix '%0' for '%1' (expected %2)">;
+def err_hlsl_unsupported_register_prefix : Error<"invalid resource class specifier '%0' used; expected 't', 'u', 'b', or 's'">;
+def err_hlsl_unsupported_register_resource_type : Error<"invalid resource '%0' used">;
 def err_hlsl_unsupported_register_number : Error<"register number should be an integer">;
 def err_hlsl_expected_space : Error<"invalid space specifier '%0' used; expected 'space' followed by an integer, like space1">;
 def warn_hlsl_packoffset_mix : Warning<"cannot mix packoffset elements with nonpackoffset elements in a cbuffer">,
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 4d6958a1be3e5..d3d36d04d1019 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -22,6 +22,7 @@
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Sema/Scope.h"
 #include "clang/Sema/SemaBase.h"
+#include "clang/Sema/Sema.h"
 #include <initializer_list>
 
 namespace clang {
@@ -31,6 +32,7 @@ class SemaHLSL : public SemaBase {
 public:
   SemaHLSL(Sema &S);
 
+  HLSLResourceAttr *mergeHLSLResourceAttr(bool CBuffer);
   Decl *ActOnStartBuffer(Scope *BufferScope, bool CBuffer, SourceLocation KwLoc,
                          IdentifierInfo *Ident, SourceLocation IdentLoc,
                          SourceLocation LBrace);
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index a2b29a7bdf505..b82cd8373403a 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -490,23 +490,24 @@ void HLSLExternalSemaSource::defineTrivialHLSLTypes() {
 }
 
 /// Set up common members and attributes for buffer types
-static BuiltinTypeDeclBuilder setupBufferType(CXXRecordDecl *Decl, Sema &S,
-                                              ResourceClass RC, ResourceKind RK,
-                                              bool IsROV) {
+static BuiltinTypeDeclBuilder setupBufferHandle(CXXRecordDecl *Decl, Sema &S,
+                                                ResourceClass RC) {
   return BuiltinTypeDeclBuilder(Decl)
       .addHandleMember()
-      .addDefaultHandleConstructor(S, RC)
-      .annotateResourceClass(RC, RK, IsROV);
+      .addDefaultHandleConstructor(S, RC);
 }
 
 void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
   CXXRecordDecl *Decl;
-  Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWBuffer")
-             .addSimpleTemplateParams(*SemaPtr, {"element_type"})
-             .Record;
+  Decl =
+      BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RWBuffer")
+          .addSimpleTemplateParams(*SemaPtr, {"element_type"})
+          .annotateResourceClass(ResourceClass::UAV, ResourceKind::TypedBuffer,
+                                 /*IsROV=*/false)
+          .Record;
+
   onCompletion(Decl, [this](CXXRecordDecl *Decl) {
-    setupBufferType(Decl, *SemaPtr, ResourceClass::UAV,
-                    ResourceKind::TypedBuffer, /*IsROV=*/false)
+    setupBufferHandle(Decl, *SemaPtr, ResourceClass::UAV)
         .addArraySubscriptOperators()
         .completeDefinition();
   });
@@ -514,10 +515,11 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
   Decl =
       BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "RasterizerOrderedBuffer")
           .addSimpleTemplateParams(*SemaPtr, {"element_type"})
+          .annotateResourceClass(ResourceClass::UAV, ResourceKind::TypedBuffer,
+                                 /*IsROV=*/true)
           .Record;
   onCompletion(Decl, [this](CXXRecordDecl *Decl) {
-    setupBufferType(Decl, *SemaPtr, ResourceClass::UAV,
-                    ResourceKind::TypedBuffer, /*IsROV=*/true)
+    setupBufferHandle(Decl, *SemaPtr, ResourceClass::UAV)
         .addArraySubscriptOperators()
         .completeDefinition();
   });
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index eebe17a5b4bf7..d2b2f163231c9 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -29,6 +29,25 @@ using namespace clang;
 
 SemaHLSL::SemaHLSL(Sema &S) : SemaBase(S) {}
 
+HLSLResourceAttr *SemaHLSL::mergeHLSLResourceAttr(bool CBuffer) {
+  // cbuffer case
+  if (CBuffer) {
+    HLSLResourceAttr *attr = HLSLResourceAttr::CreateImplicit(
+        getASTContext(), llvm::hlsl::ResourceClass::CBuffer,
+        llvm::hlsl::ResourceKind::CBuffer,
+        /*IsROV=*/false);
+    return attr;
+  }
+  // tbuffer case
+  else {
+    HLSLResourceAttr *attr = HLSLResourceAttr::CreateImplicit(
+        getASTContext(), llvm::hlsl::ResourceClass::SRV,
+        llvm::hlsl::ResourceKind::TBuffer,
+        /*IsROV=*/false);
+    return attr;
+  }
+}
+
 Decl *SemaHLSL::ActOnStartBuffer(Scope *BufferScope, bool CBuffer,
                                  SourceLocation KwLoc, IdentifierInfo *Ident,
                                  SourceLocation IdentLoc,
@@ -38,6 +57,10 @@ Decl *SemaHLSL::ActOnStartBuffer(Scope *BufferScope, bool CBuffer,
   HLSLBufferDecl *Result = HLSLBufferDecl::Create(
       getASTContext(), LexicalParent, CBuffer, KwLoc, Ident, IdentLoc, LBrace);
 
+  HLSLResourceAttr *NewAttr = mergeHLSLResourceAttr(CBuffer);
+  if (NewAttr)
+    Result->addAttr(NewAttr);
+
   SemaRef.PushOnScopeChains(Result, BufferScope);
   SemaRef.PushDeclContext(BufferScope, Result);
 
@@ -437,7 +460,206 @@ void SemaHLSL::handleShaderAttr(Decl *D, const ParsedAttr &AL) {
     D->addAttr(NewAttr);
 }
 
+struct register_binding_flags {
+  bool resource = false;
+  bool udt = false;
+  bool other = false;
+  bool basic = false;
+
+  bool srv = false;
+  bool uav = false;
+  bool cbv = false;
+  bool sampler = false;
+
+  bool contains_numeric = false;
+  bool default_globals = false;
+};
+
+bool isDeclaredWithinCOrTBuffer(const Decl *decl) {
+  if (!decl)
+    return false;
+
+  // Traverse up the parent contexts
+  const DeclContext *context = decl->getDeclContext();
+  while (context) {
+    if (isa<HLSLBufferDecl>(context)) {
+      return true;
+    }
+    context = context->getParent();
+  }
+
+  return false;
+}
+
+const HLSLResourceAttr *
+getHLSLResourceAttrFromVarDecl(VarDecl *SamplerUAVOrSRV) {
+  const Type *Ty = SamplerUAVOrSRV->getType()->getPointeeOrArrayElementType();
+  if (!Ty)
+    llvm_unreachable("Resource class must have an element type.");
+
+  if (const BuiltinType *BTy = dyn_cast<BuiltinType>(Ty)) {
+    /* QualType QT = SamplerUAVOrSRV->getType();
+    PrintingPolicy PP = S.getPrintingPolicy();
+    std::string typestr = QualType::getAsString(QT.split(), PP);
+
+    S.Diag(ArgLoc, diag::err_hlsl_unsupported_register_resource_type)
+        << typestr;
+    return; */
+    return nullptr;
+  }
+
+  const CXXRecordDecl *TheRecordDecl = Ty->getAsCXXRecordDecl();
+  if (!TheRecordDecl)
+    llvm_unreachable("Resource class should have a resource type declaration.");
+
+  if (auto TDecl = dyn_cast<ClassTemplateSpecializationDecl>(TheRecordDecl))
+    TheRecordDecl = TDecl->getSpecializedTemplate()->getTemplatedDecl();
+  TheRecordDecl = TheRecordDecl->getCanonicalDecl();
+  const auto *Attr = TheRecordDecl->getAttr<HLSLResourceAttr>();
+  return Attr;
+}
+
+void traverseType(QualType T, register_binding_flags &r) {
+  if (T->isIntegralOrEnumerationType() || T->isFloatingType()) {
+    r.contains_numeric = true;
+    return;
+  } else if (const RecordType *RT = T->getAs<RecordType>()) {
+    RecordDecl *SubRD = RT->getDecl();
+    if (auto TDecl = dyn_cast<ClassTemplateSpecializationDecl>(SubRD)) {
+      auto TheRecordDecl = TDecl->getSpecializedTemplate()->getTemplatedDecl();
+      TheRecordDecl = TheRecordDecl->getCanonicalDecl();
+      const auto *Attr = TheRecordDecl->getAttr<HLSLResourceAttr>();
+      llvm::hlsl::ResourceClass DeclResourceClass = Attr->getResourceClass();
+      switch (DeclResourceClass) {
+      case llvm::hlsl::ResourceClass::SRV: {
+        r.srv = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::UAV: {
+        r.uav = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::CBuffer: {
+        r.cbv = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::Sampler: {
+        r.sampler = true;
+        break;
+      }     
+      }
+    }
+
+    else if (SubRD->isCompleteDefinition()) {
+      for (auto Field : SubRD->fields()) {
+        QualType T = Field->getType();
+        traverseType(T, r);
+      }
+    }
+  }
+}
+
+void setResourceClassFlagsFromRecordDecl(register_binding_flags &r,
+                                         const RecordDecl *RD) {
+  if (!RD)
+    return;
+
+  if (RD->isCompleteDefinition()) {
+    for (auto Field : RD->fields()) {
+      QualType T = Field->getType();
+      traverseType(T, r);
+    }
+  }
+}
+
+register_binding_flags HLSLFillRegisterBindingFlags(Sema &S, Decl *D) {
+  register_binding_flags r;
+  if (!isDeclaredWithinCOrTBuffer(D)) {
+    // make sure the type is a basic / numeric type
+    if (VarDecl *v = dyn_cast<VarDecl>(D)) {
+      QualType t = v->getType();
+      // a numeric variable will inevitably end up in $Globals buffer
+      if (t->isIntegralType(S.getASTContext()) || t->isFloatingType())
+        r.default_globals = true;
+    }
+  }
+  // Cbuffers and Tbuffers are HLSLBufferDecl types
+  HLSLBufferDecl *CBufferOrTBuffer = dyn_cast<HLSLBufferDecl>(D);
+  // Samplers, UAVs, and SRVs are VarDecl types
+  VarDecl *SamplerUAVOrSRV = dyn_cast<VarDecl>(D);
+
+  if (CBufferOrTBuffer) {
+    r.resource = true;
+    if (CBufferOrTBuffer->isCBuffer())
+      r.cbv = true;
+    else
+      r.srv = true;
+  } else if (SamplerUAVOrSRV) {
+    const HLSLResourceAttr *res_attr =
+        getHLSLResourceAttrFromVarDecl(SamplerUAVOrSRV);
+    if (res_attr) {
+      llvm::hlsl::ResourceClass DeclResourceClass =
+          res_attr->getResourceClass();
+      r.resource = true;
+      switch (DeclResourceClass) {
+      case llvm::hlsl::ResourceClass::SRV: {
+        r.srv = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::UAV: {
+        r.uav = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::CBuffer: {
+        r.cbv = true;
+        break;
+      }
+      case llvm::hlsl::ResourceClass::Sampler: {
+        r.sampler = true;
+        break;
+      }      
+      }
+    } else {
+      if (SamplerUAVOrSRV->getType()->isBuiltinType())
+        r.basic = true;
+      else if (SamplerUAVOrSRV->getType()->isAggregateType()) {
+        r.udt = true;
+        QualType VarType = SamplerUAVOrSRV->getType();
+        if (const RecordType *RT = VarType->getAs<RecordType>()) {
+          const RecordDecl *RD = RT->getDecl();
+          // recurse through members, set appropriate resource class flags.
+          setResourceClassFlagsFromRecordDecl(r, RD);
+        }
+      } else
+        r.other = true;
+    }
+  } else {
+    llvm_unreachable("unknown decl type");
+  }
+  return r;
+}
+
+static void DiagnoseHLSLResourceRegType(Sema &S, SourceLocation &ArgLoc,
+                                        Decl *D, StringRef &Slot) {
+
+  register_binding_flags f = HLSLFillRegisterBindingFlags(S, D);
+  // Samplers, UAVs, and SRVs are VarDecl types
+  VarDecl *SamplerUAVOrSRV = dyn_cast<VarDecl>(D);
+  // Cbuffers and Tbuffers are HLSLBufferDecl types
+  HLSLBufferDecl *CBufferOrTBuffer = dyn_cast<HLSLBufferDecl>(D);
+  if (!SamplerUAVOrSRV && !CBufferOrTBuffer)
+    return;
+
+  // TODO: emit diagnostic code based on the flags set in f.
+}
+
+
 void SemaHLSL::handleResourceBindingAttr(Decl *D, const ParsedAttr &AL) {
+  if (dyn_cast<VarDecl>(D)) {  
+    if (SemaRef.RequireCompleteType(D->getBeginLoc(), cast<ValueDecl>(D)->getType(),
+                              diag::err_incomplete_type))
+      return;
+  }
   StringRef Space = "space0";
   StringRef Slot = "";
 
@@ -470,13 +692,15 @@ void SemaHLSL::handleResourceBindingAttr(Decl *D, const ParsedAttr &AL) {
   // Validate.
   if (!Slot.empty()) {
     switch (Slot[0]) {
+    case 't':
     case 'u':
     case 'b':
     case 's':
-    case 't':
+    case 'c':
+    case 'i':
       break;
     default:
-      Diag(ArgLoc, diag::err_hlsl_unsupported_register_type)
+      Diag(ArgLoc, diag::err_hlsl_unsupported_register_prefix)
           << Slot.substr(0, 1);
       return;
     }
@@ -500,8 +724,8 @@ void SemaHLSL::handleResourceBindingAttr(Decl *D, const ParsedAttr &AL) {
     return;
   }
 
-  // FIXME: check reg type match decl. Issue
-  // https://github.com/llvm/llvm-project/issues/57886.
+  DiagnoseHLSLResourceRegType(SemaRef, ArgLoc, D, Slot);
+
   HLSLResourceBindingAttr *NewAttr =
       HLSLResourceBindingAttr::Create(getASTContext(), Slot, Space, AL);
   if (NewAttr)
diff --git a/clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl b/clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl
index a98dc0f4ce431..727d505471bcb 100644
--- a/clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl
+++ b/clang/test/AST/HLSL/ast-dump-comment-cbuffe-tbufferr.hlsl
@@ -38,12 +38,14 @@ tbuffer B {
 }
 
 // AST:HLSLBufferDecl {{.*}}:11:1, line:20:1> line:11:9 cbuffer A
+// AST-NEXT:-HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit CBuffer CBuffer
 // AST-NEXT:FullComment {{.*}}<line:10:4, col:17>
 // AST-NEXT:`-ParagraphComment {{.*}}<col:4, col:17>
 // AST-NEXT:`-TextComment {{.*}}<col:4, col:17> Text=" CBuffer decl."
 // AST-NEXT:-VarDecl {{.*}}<line:15:5, col:11> col:11 a 'float'
 // AST-NEXT:`-VarDecl {{.*}}<line:19:5, col:9> col:9 b 'int'
 // AST-NEXT:HLSLBufferDecl {{.*}}<line:29:1, line:38:1> line:29:9 tbuffer B
+// AST-NEXT:-HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit SRV TBuffer
 // AST-NEXT:-FullComment {{.*}}<line:28:4, col:17>
 // AST-NEXT: `-ParagraphComment {{.*}}<col:4, col:17>
 // AST-NEXT:  `-TextComment {{.*}}<col:4, col:17> Text=" TBuffer decl."
diff --git a/clang/test/AST/HLSL/cbuffer_tbuffer.hlsl b/clang/test/AST/HLSL/cbuffer_tbuffer.hlsl
index 7204dcd16e0a9..a67688d510ea6 100644
--- a/clang/test/AST/HLSL/cbuffer_tbuffer.hlsl
+++ b/clang/test/AST/HLSL/cbuffer_tbuffer.hlsl
@@ -1,12 +1,14 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -ast-dump -o - %s | FileCheck %s
 
-// CHECK:HLSLBufferDecl 0x[[CB:[0-9a-f]+]] {{.*}} line:5:9 cbuffer CB
+// CHECK:HLSLBufferDecl 0x[[CB:[0-9a-f]+]] {{.*}} line:6:9 cbuffer CB
+// CHECK-NEXT:HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit CBuffer CBuffer
 // CHECK-NEXT:VarDecl 0x[[A:[0-9a-f]+]] {{.*}} col:9 used a 'float'
 cbuffer CB {
   float a;
 }
 
-// CHECK:HLSLBufferDecl 0x[[TB:[0-9a-f]+]] {{.*}} line:11:9 tbuffer TB
+// CHECK:HLSLBufferDecl 0x[[TB:[0-9a-f]+]] {{.*}} line:13:9 tbuffer TB
+// CHECK-NEXT:HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit SRV TBuffer
 // CHECK-NEXT:VarDecl 0x[[B:[0-9a-f]+]] {{.*}} col:9 used b 'float'
 tbuffer TB {
   float b;
diff --git a/clang/test/AST/HLSL/packoffset.hlsl b/clang/test/AST/HLSL/packoffset.hlsl
index 060288c2f7f76..1dc99620c55c4 100644
--- a/clang/test/AST/HLSL/packoffset.hlsl
+++ b/clang/test/AST/HLSL/packoffset.hlsl
@@ -4,6 +4,7 @@
 // CHECK: HLSLBufferDecl {{.*}} cbuffer A
 cbuffer A
 {
+    // CHECK-NEXT:-HLSLResourceAttr {{.*}} <<invalid sloc>> line:5:9 cbuffer A
     // CHECK-NEXT: VarDecl {{.*}} A1 'float4'
     // CHECK-NEXT: HLSLPackOffsetAttr {{.*}} 0 0
     float4 A1 : packoffset(c);
diff --git a/clang/test/AST/HLSL/pch_hlsl_buffer.hlsl b/clang/test/AST/HLSL/pch_hlsl_buffer.hlsl
index e9a6ea1a16312..e264241e62e92 100644
--- a/clang/test/AST/HLSL/pch_hlsl_buffer.hlsl
+++ b/clang/test/AST/HLSL/pch_hlsl_buffer.hlsl
@@ -17,8 +17,10 @@ float foo() {
 }
 // Make sure cbuffer/tbuffer works for PCH.
 // CHECK:HLSLBufferDecl 0x{{[0-9a-f]+}} <{{.*}}:7:1, line:9:1> line:7:9 imported <undeserialized declarations> cbuffer A
+// CHECK-NEXT:HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit CBuffer CBuffer
 // CHECK-NEXT:`-VarDecl 0x[[A:[0-9a-f]+]] <line:8:3, col:9> col:9 imported used a 'float'
 // CHECK-NEXT:HLSLBufferDecl 0x{{[0-9a-f]+}} <line:11:1, line:13:1> line:11:9 imported <undeserialized declarations> tbuffer B
+// CHECK-NEXT:HLSLResourceAttr {{.*}} <<invalid sloc>> Implicit SRV TBuffer
 // CHECK-NEXT:`-VarDecl 0x[[B:[0-9a-f]+]] <line:12:3, col:9> col:9 imported used b 'float'
 // CHECK-NEXT:FunctionDecl 0x{{[0-9a-f]+}} <line:15:1, line:17:1> line:15:7 imported foo 'float ()'
 // CHECK-NEXT:CompoundStmt 0x{{[0-9a-f]+}} <col:13, line:17:1>
diff --git a/clang/test/AST/HLSL/resource_binding_attr.hlsl b/clang/test/AST/HLSL/resource_binding_attr.hlsl
index 71900f2dbda55..9752494f5adc9 100644
--- a/clang/test/AST/HLSL/resource_binding_attr.hlsl
+++ b/clang/test/AST/HLSL/resource_binding_attr.hlsl
@@ -1,13 +1,15 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -ast-dump -o - %s | FileCheck %s
 
-// CHECK:HLSLBufferDecl 0x[[CB:[0-9a-f]+]] {{.*}} line:6:9 cbuffer CB
+// CHECK:HLSLBufferDecl 0x[[CB:[0-9a-f]+]] {{.*}} line:7:9 cbuffer CB
+// CHECK-NEXT:HLSLResourceAttr 0x[[CB:[0-9a-f]+]] {{.*}} Implicit CBuffer CBuffer
 // CHECK-NEXT:HLSLResourceBindingAttr 0x{{[0-9a-f]+}} <col:14> "b3" "space2"
 // CHECK-NEXT:VarDecl 0x[[A:[0-9a-f]+]] {{.*}} col:9 used a 'float'
 cbuffer CB : register(b3, space2) {
   float a;
 }
 
-// CHECK:HLSLBufferDecl 0x[[TB:[0-9a-f]+]] {{.*}} line:13:9 tbuffer TB
+// CHECK:HLSLBufferDecl 0x[[TB:[0-9a-f]+]] {{.*}} line:15:9 tbuffer TB
+// CHECK-NEXT:HLSLResourceAttr 0x[[CB:[0-9a-f]+]] {{.*}} Implicit SRV TBuffer
 // CHECK-NEXT:HLSLResourceBindingAttr 0x{{[0-9a-f]+}} <col:14> "t2" "space1"
 // CHECK-NEXT:VarDecl 0x[[B:[0-9a-f]+]] {{.*}} col:9 used b 'float'
 tbuffer TB : register(t2, space1) {
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
index 2f8aa098db701..58a1f3f1f64d7 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
@@ -1,9 +1,9 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - -fsyntax-only %s -verify
 
-// expected-error@+1 {{invalid resource class specifier 'c' used; expected 'b', 's', 't', or 'u'}}
+// FIXME: emit a diagnostic because float doesn't match the 'c' register type
 float a : register(c0, space1);
 
-// expected-error@+1 {{invalid resource class specifier 'i' used; expected 'b', 's', 't', or 'u'}}
+// FIXME: emit a diagnostic because cbuffer doesn't match the 'i' register type
 cbuffer b : register(i0) {
 
 }
@@ -33,28 +33,27 @@ cbuffer C : register(b 2) {}
 // expected-error@+1 {{wrong argument format for hlsl attribute, use space3 instead}}
 cbuffer D : register(b 2, space 3) {}
 
-// expected-warning@+1 {{'register' attribute only applies to cbuffer/tbuffer and external global variables}}
+// expected-error@+1 {{'register' attribute only applies to cbuffer/tbuffer and external global variables}}
 static RWBuffer<float> U : register(u5);
 
 void foo() {
-  // expected-warning@+1 {{'register' attribute only applies to cbuffer/tbuffer and external global vari...
[truncated]

@bob80905 bob80905 changed the title Implement resource binding type prefix mismatch errors Implement resource binding type prefix mismatch flag setting logic Jun 28, 2024
@bob80905 bob80905 self-assigned this Jun 28, 2024
Copy link

github-actions bot commented Jun 28, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@damyanp damyanp left a comment

Choose a reason for hiding this comment

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

I'm a bit confused by this change. It seems that some tweaks have been made to the diagnostics, and something code around register_binding_flags has been added, but doesn't seem to be tested at all.

It seems it should be possible to slice this so that changes do come in with tests and aren't entirely around anticipating future functionality.

Copy link
Contributor

@damyanp damyanp left a comment

Choose a reason for hiding this comment

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

I've not finished reviewing all of it, but here's a first batch of observations.

Copy link
Collaborator

@llvm-beanz llvm-beanz left a comment

Choose a reason for hiding this comment

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

I’ll give this a deeper read tomorrow, but I’ve left some comments mostly on coding convention and style issues.

ResourceClass RC, ResourceKind RK,
bool IsROV) {
static BuiltinTypeDeclBuilder setupBufferHandle(CXXRecordDecl *Decl, Sema &S,
ResourceClass RC) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Am I correct in reading this that you’re moving the attribute from the onCompletion handler to the forward declaration so that the type is always attributed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's right! The annotation assignment didn't occur quickly enough to see the annotation at the location where I needed to check for the annotation.

Comment on lines 41 to 55
HLSLResourceAttr *NewAttr;
if (CBuffer) {
NewAttr = HLSLResourceAttr::CreateImplicit(
getASTContext(), llvm::hlsl::ResourceClass::CBuffer,
llvm::hlsl::ResourceKind::CBuffer,
/*IsROV=*/false);
}
// tbuffer case
else {
NewAttr = HLSLResourceAttr::CreateImplicit(
getASTContext(), llvm::hlsl::ResourceClass::SRV,
llvm::hlsl::ResourceKind::TBuffer,
/*IsROV=*/false);
}
Result->addAttr(NewAttr);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
HLSLResourceAttr *NewAttr;
if (CBuffer) {
NewAttr = HLSLResourceAttr::CreateImplicit(
getASTContext(), llvm::hlsl::ResourceClass::CBuffer,
llvm::hlsl::ResourceKind::CBuffer,
/*IsROV=*/false);
}
// tbuffer case
else {
NewAttr = HLSLResourceAttr::CreateImplicit(
getASTContext(), llvm::hlsl::ResourceClass::SRV,
llvm::hlsl::ResourceKind::TBuffer,
/*IsROV=*/false);
}
Result->addAttr(NewAttr);
auto RC = CBuffer ? llvm::hlsl::ResourceClass::CBuffer : llvm::hlsl::ResourceClass::SRV;
auto RK = CBuffer ? llvm::hlsl::ResourceKind::CBuffer : llvm::hlsl::ResourceKind::TBuffer
D->addAttr(HLSLResourceAttr::CreateImplicit(getASTContext(),RC, RK, /*IsROV=*/false));

nit: the formatting for this is off, but I think this is a simpler way to write it.


// Traverse up the parent contexts
const DeclContext *context = decl->getDeclContext();
while (context) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What are the cases where we need to traverse up more than one level here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there is such a case, I've removed the while loop.

Comment on lines 489 to 490
if (!Ty)
llvm_unreachable("Resource class must have an element type.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

A condition body should never just be an assert or unreachable.

Suggested change
if (!Ty)
llvm_unreachable("Resource class must have an element type.");
assert(Ty && "Resource class must have an element type.");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed, fixed.

Comment on lines 492 to 518
if (const BuiltinType *BTy = dyn_cast<BuiltinType>(Ty)) {
return nullptr;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Avoid braces on simple single-line expressions (see: https://llvm.org/docs/CodingStandards.html#don-t-use-braces-on-simple-single-statement-bodies-of-if-else-loop-statements).

nit: Use auto for cases where the type is obvious (see: https://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable).

Suggested change
if (const BuiltinType *BTy = dyn_cast<BuiltinType>(Ty)) {
return nullptr;
}
if (const auto *BTy = dyn_cast<BuiltinType>(Ty))
return nullptr;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

if (T->isIntegralOrEnumerationType() || T->isFloatingType()) {
r.ContainsNumeric = true;
return;
} else if (const RecordType *RT = T->getAs<RecordType>()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: Don’t use else after a return (see: https://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return)

This functions nesting also gets pretty deep, you could use an early return to flatten it (see: https://llvm.org/docs/CodingStandards.html#use-early-exits-and-continue-to-simplify-code).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

const auto *Attr = TheRecordDecl->getAttr<HLSLResourceAttr>();
llvm::hlsl::ResourceClass DeclResourceClass = Attr->getResourceClass();
switch (DeclResourceClass) {
case llvm::hlsl::ResourceClass::SRV: {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: the braces on these case labels add a lot of extra braces, but the scoping isn’t particularly useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍


bool Srv = false;
bool Uav = false;
bool Cbv = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

UDT, SRV, UAV, and CBV are all initialisms, it probably makes the most sense to fully capitalize them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

r.Other = true;
}
} else {
llvm_unreachable("unknown decl type");
Copy link
Collaborator

Choose a reason for hiding this comment

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

How would this happen? Can we instead write this as an assert earlier in the function that more clearly communicates the expected requirements?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turns out that there's an assert for this in the caller function before this function is called, so this is impossible to reach, and I'll remove it.

Copy link
Contributor

Choose a reason for hiding this comment

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

But this function still makes this assumption? It'd be good to capture that here so that it is robust against future changes to the code.

@bob80905 bob80905 changed the title Implement resource binding type prefix mismatch flag setting logic Implement resource binding type prefix mismatch diagnostic infrastructure Jul 9, 2024
float a : register(c0, space1);
// valid, The register keyword in this statement isn't binding a resource, rather it is
// specifying a constant register binding offset within the $Globals cbuffer, which is legacy behavior from DX9.
float a : register(c0);
Copy link
Contributor

@python3kgae python3kgae Jul 10, 2024

Choose a reason for hiding this comment

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

Do we have a test for static global?
Something like

static float sa : register(c1);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a test case to this file to test this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got another test idea

 groupshared float a[10] : register(c1);

Sorry for not getting all the ideas at once :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, added the test case along with the error it produced. Treating groupshared variables as other.
Also added some test cases for basic types inside arrays.

@bob80905 bob80905 force-pushed the validate_register_binding_hlsl01 branch from bd711f9 to a702ac8 Compare July 12, 2024 22:56
switch (Slot[0]) {
case 't':
case 'T':
return 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we have an enum for this instead of use 0, 1, 2...?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added an enum, not sure what to name the register types for c and i though.

@bob80905
Copy link
Contributor Author

I've created an issue here to test the space parameter:
#104521

@bob80905 bob80905 force-pushed the validate_register_binding_hlsl01 branch from bceef7d to 9b72907 Compare August 21, 2024 17:56
@damyanp damyanp requested a review from python3kgae August 22, 2024 01:12
RWBuffer<int> r1;
};
// expected-warning@+1{{binding type 't' only applies to types containing srv resources}}
Eg14 e14 : register(t9);
Copy link
Member

Choose a reason for hiding this comment

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

Four of the test files are missing an empty new line at the end.

Comment on lines 41 to 42
};
Bar b;
Copy link
Member

Choose a reason for hiding this comment

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

formatting

Comment on lines 669 to 671
const clang::Type *TheBaseType = TheVarDecl->getType().getTypePtr();
while (TheBaseType->isArrayType())
TheBaseType = TheBaseType->getArrayElementTypeNoTypeQual();
Copy link
Member

Choose a reason for hiding this comment

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

TheBaseType is not used until line 679: if (TheBaseType->isArithmeticType()). Can you move this closer to where it is used?

template <typename T>
static const T *
getSpecifiedHLSLAttrFromVarDeclOrRecordDecl(VarDecl *VD,
RecordDecl *TheRecordDecl) {
Copy link
Member

Choose a reason for hiding this comment

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

I agree with @damyanp. This function seems to always be called with one of the arguments null. Why not have getAttributeFromVarDecl and getAttributeFromRecordDecl, and the first one would call the other in the VarDecl case?

Additionally, getAttributeFromRecordDecl could also detect the attibute on the record decl itself, avoiding the separate check on line 598 (with comment // otherwise, check if the member of the UDT itself has a resource class attr).

Also, this seems to find just the first instance of the attribute. Is that ok? What if there are more and with conflicting resource classes? Have you considered passing in the RegisterBindingFlags by reference and let the function fill in the resource class and other info, instead of extracting a single attribute?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Sounds good.
  2. I'll see if this works.
  3. It should be ok that the first attribute is all that's detected. We don't expect users to use user-defined resources that have made use of the spellable attributes, and have defined more than one resource class for said attribute. All HLSL resources should have exactly one resource class attribute, so there will never be a valid case where there are 2 attributes and we should've picked the second one.

Copy link
Member

Choose a reason for hiding this comment

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

  1. It should be ok that the first attribute is all that's detected. We don't expect users to use user-defined resources that have made use of the spellable attributes, and have defined more than one resource class for said attribute. All HLSL resources should have exactly one resource class attribute, so there will never be a valid case where there are 2 attributes and we should've picked the second one.

I guess that's ok for now. I'll approve to get this PR in to unblock the milestone, but we might want to go revisit this later.

Comment on lines +816 to +827
static RegisterType ExpectedRegisterTypesForResourceClass[] = {
RegisterType::SRV,
RegisterType::UAV,
RegisterType::CBuffer,
RegisterType::Sampler,
};
assert((int)DeclResourceClass <
std::size(ExpectedRegisterTypesForResourceClass) &&
"DeclResourceClass has unexpected value");

RegisterType ExpectedRegisterType =
ExpectedRegisterTypesForResourceClass[(int)DeclResourceClass];
Copy link
Member

@hekota hekota Aug 22, 2024

Choose a reason for hiding this comment

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

I think this conversion could be simplified without the static array. How about declaring RegisterType with the values from RegisterClass and then have a no-op type-safe conversion function with the assert?

enum class RegisterType { 
  SRV = static_cast<int>(llvm::hlsl::ResourceClass::SRV),
  UAV = static_cast<int>(llvm::hlsl::ResourceClass::UAV),
  CBuffer = static_cast<int>(llvm::hlsl::ResourceClass::CBuffer),
  Sampler = static_cast<int>(llvm::hlsl::ResourceClass::Sampler)
};

static RegisterType ResourceClassToRegisterType(llvm::hlsl::ResourceClass RC) {
  assert(static_cast<int>(RC) >= static_cast<int>(RegisterType::SRV) &&
         static_cast<int>(RC) <= static_cast<int>(RegisterType::Sampler) &&
           "unexpected ResourceClass value");
  return static_cast<RegisterType>(static_cast<int>(RC));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my opinion it looks a little more complicated.

Copy link
Member

Choose a reason for hiding this comment

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

That might be, but the actual code executed for the conversion is a no-op and there is no static array to allocate and initialize.

Copy link
Contributor

@damyanp damyanp left a comment

Choose a reason for hiding this comment

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

Copy link
Contributor

@damyanp damyanp left a comment

Choose a reason for hiding this comment

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

Thank you!

@bob80905 bob80905 merged commit ebc4a66 into llvm:main Aug 23, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants