-
Notifications
You must be signed in to change notification settings - Fork 14.3k
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
Implement resource binding type prefix mismatch diagnostic infrastructure #97103
Conversation
@llvm/pr-subscribers-clang @llvm/pr-subscribers-hlsl Author: Joshua Batista (bob80905) ChangesThere 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. 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:
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]
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this 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.
There was a problem hiding this 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.
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
clang/lib/Sema/SemaHLSL.cpp
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
clang/lib/Sema/SemaHLSL.cpp
Outdated
|
||
// Traverse up the parent contexts | ||
const DeclContext *context = decl->getDeclContext(); | ||
while (context) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
clang/lib/Sema/SemaHLSL.cpp
Outdated
if (!Ty) | ||
llvm_unreachable("Resource class must have an element type."); |
There was a problem hiding this comment.
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.
if (!Ty) | |
llvm_unreachable("Resource class must have an element type."); | |
assert(Ty && "Resource class must have an element type."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, fixed.
clang/lib/Sema/SemaHLSL.cpp
Outdated
if (const BuiltinType *BTy = dyn_cast<BuiltinType>(Ty)) { | ||
return nullptr; | ||
} |
There was a problem hiding this comment.
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).
if (const BuiltinType *BTy = dyn_cast<BuiltinType>(Ty)) { | |
return nullptr; | |
} | |
if (const auto *BTy = dyn_cast<BuiltinType>(Ty)) | |
return nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
clang/lib/Sema/SemaHLSL.cpp
Outdated
if (T->isIntegralOrEnumerationType() || T->isFloatingType()) { | ||
r.ContainsNumeric = true; | ||
return; | ||
} else if (const RecordType *RT = T->getAs<RecordType>()) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
clang/lib/Sema/SemaHLSL.cpp
Outdated
const auto *Attr = TheRecordDecl->getAttr<HLSLResourceAttr>(); | ||
llvm::hlsl::ResourceClass DeclResourceClass = Attr->getResourceClass(); | ||
switch (DeclResourceClass) { | ||
case llvm::hlsl::ResourceClass::SRV: { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
clang/lib/Sema/SemaHLSL.cpp
Outdated
|
||
bool Srv = false; | ||
bool Uav = false; | ||
bool Cbv = false; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
clang/lib/Sema/SemaHLSL.cpp
Outdated
r.Other = true; | ||
} | ||
} else { | ||
llvm_unreachable("unknown decl type"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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); |
There was a problem hiding this comment.
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);
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :(
There was a problem hiding this comment.
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.
bd711f9
to
a702ac8
Compare
clang/lib/Sema/SemaHLSL.cpp
Outdated
switch (Slot[0]) { | ||
case 't': | ||
case 'T': | ||
return 0; |
There was a problem hiding this comment.
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...?
There was a problem hiding this comment.
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.
I've created an issue here to test the space parameter: |
… emit duplicate diagnostics
bceef7d
to
9b72907
Compare
RWBuffer<int> r1; | ||
}; | ||
// expected-warning@+1{{binding type 't' only applies to types containing srv resources}} | ||
Eg14 e14 : register(t9); |
There was a problem hiding this comment.
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.
}; | ||
Bar b; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatting
clang/lib/Sema/SemaHLSL.cpp
Outdated
const clang::Type *TheBaseType = TheVarDecl->getType().getTypePtr(); | ||
while (TheBaseType->isArrayType()) | ||
TheBaseType = TheBaseType->getArrayElementTypeNoTypeQual(); |
There was a problem hiding this comment.
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?
clang/lib/Sema/SemaHLSL.cpp
Outdated
template <typename T> | ||
static const T * | ||
getSpecifiedHLSLAttrFromVarDeclOrRecordDecl(VarDecl *VD, | ||
RecordDecl *TheRecordDecl) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Sounds good.
- I'll see if this works.
- 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
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]; |
There was a problem hiding this comment.
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));
}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few open issues I'd like to see addressed before approving this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
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