Author: Joshua Batista
Date: 2026-01-09T13:37:49-08:00
New Revision: 719006a1950ad190882483a8342be4f593164c94

URL: 
https://github.com/llvm/llvm-project/commit/719006a1950ad190882483a8342be4f593164c94
DIFF: 
https://github.com/llvm/llvm-project/commit/719006a1950ad190882483a8342be4f593164c94.diff

LOG: [HLSL][Sema] Validate that occupied register numbers never exceed 
UINT32_MAX (#174028)

This PR adds validation for register numbers.
Register numbers ought never to exceed UINT32_MAX, or 4294967295
Additionally, resource arrays will have each resource element bound
sequentially, and those resource's register numbers should not exceed
UINT32_MAX, or 4294967295. Even though not explicitly given a register
number, their effective register number is also validated.
This accounts for nested resource declarations and resource arrays too.

Fixes https://github.com/llvm/llvm-project/issues/136809

Added: 
    clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl

Modified: 
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Sema/SemaHLSL.cpp
    clang/test/SemaHLSL/resource_binding_attr_error.hlsl

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fdd9b9cd3a996..cd0b5b6343b9e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13341,6 +13341,7 @@ def warn_hlsl_register_type_c_packoffset: 
Warning<"binding type 'c' ignored in b
 def warn_hlsl_deprecated_register_type_b: Warning<"binding type 'b' only 
applies to constant buffers. The 'bool constant' binding type is no longer 
supported">, InGroup<LegacyConstantRegisterBinding>, DefaultError;
 def warn_hlsl_deprecated_register_type_i: Warning<"binding type 'i' ignored. 
The 'integer constant' binding type is no longer supported">, 
InGroup<LegacyConstantRegisterBinding>, DefaultError;
 def err_hlsl_unsupported_register_number : Error<"register number should be an 
integer">;
+def err_hlsl_register_number_too_large : Error<"register number should not 
exceed 4294967295">;
 def err_hlsl_expected_space : Error<"invalid space specifier '%0' used; 
expected 'space' followed by an integer, like space1">;
 def err_hlsl_space_on_global_constant : Error<"register space cannot be 
specified on global constants">;
 def warn_hlsl_implicit_binding : Warning<"resource has implicit register 
binding">, InGroup<HLSLImplicitBinding>, DefaultIgnore;

diff  --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index a6de1cd550212..0b6f1d8075985 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2361,6 +2361,123 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, 
SourceLocation &ArgLoc,
   return ValidateMultipleRegisterAnnotations(S, D, RegType);
 }
 
+// return false if the slot count exceeds the limit, true otherwise
+static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &StartSlot,
+                                        const uint64_t &Limit,
+                                        const ResourceClass ResClass,
+                                        ASTContext &Ctx,
+                                        uint64_t ArrayCount = 1) {
+  Ty = Ty.getCanonicalType();
+  const Type *T = Ty.getTypePtr();
+
+  // Early exit if already overflowed
+  if (StartSlot > Limit)
+    return false;
+
+  // Case 1: array type
+  if (const auto *AT = dyn_cast<ArrayType>(T)) {
+    uint64_t Count = 1;
+
+    if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+      Count = CAT->getSize().getZExtValue();
+
+    QualType ElemTy = AT->getElementType();
+    return AccumulateHLSLResourceSlots(ElemTy, StartSlot, Limit, ResClass, Ctx,
+                                       ArrayCount * Count);
+  }
+
+  // Case 2: resource leaf
+  if (auto ResTy = dyn_cast<HLSLAttributedResourceType>(T)) {
+    // First ensure this resource counts towards the corresponding
+    // register type limit.
+    if (ResTy->getAttrs().ResourceClass != ResClass)
+      return true;
+
+    // Validate highest slot used
+    uint64_t EndSlot = StartSlot + ArrayCount - 1;
+    if (EndSlot > Limit)
+      return false;
+
+    // Advance SlotCount past the consumed range
+    StartSlot = EndSlot + 1;
+    return true;
+  }
+
+  // Case 3: struct / record
+  if (const auto *RT = dyn_cast<RecordType>(T)) {
+    const RecordDecl *RD = RT->getDecl();
+
+    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+      for (const CXXBaseSpecifier &Base : CXXRD->bases()) {
+        if (!AccumulateHLSLResourceSlots(Base.getType(), StartSlot, Limit,
+                                         ResClass, Ctx, ArrayCount))
+          return false;
+      }
+    }
+
+    for (const FieldDecl *Field : RD->fields()) {
+      if (!AccumulateHLSLResourceSlots(Field->getType(), StartSlot, Limit,
+                                       ResClass, Ctx, ArrayCount))
+        return false;
+    }
+
+    return true;
+  }
+
+  // Case 4: everything else
+  return true;
+}
+
+// return true if there is something invalid, false otherwise
+static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
+                                   ASTContext &Ctx, RegisterType RegTy,
+                                   unsigned &Result) {
+  uint64_t SlotNum;
+  if (SlotNumStr.getAsInteger(10, SlotNum))
+    return true;
+
+  const uint64_t Limit = UINT32_MAX;
+  if (SlotNum > Limit)
+    return true;
+
+  // after verifying the number doesn't exceed uint32max, we don't need
+  // to look further into c or i register types
+  if (RegTy == RegisterType::C || RegTy == RegisterType::I) {
+    SlotNumStr.getAsInteger(10, Result);
+    return false;
+  }
+
+  if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
+    uint64_t BaseSlot = SlotNum;
+
+    if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit,
+                                     getResourceClass(RegTy), Ctx))
+      return true;
+
+    // After AccumulateHLSLResourceSlots runs, SlotNum is now
+    // the first free slot; last used was SlotNum - 1
+    if (BaseSlot > Limit)
+      return true;
+
+    SlotNumStr.getAsInteger(10, Result);
+    return false;
+  }
+  // handle the cbuffer/tbuffer case
+  if (dyn_cast<HLSLBufferDecl>(TheDecl)) {
+    // resources cannot be put within a cbuffer, so no need
+    // to analyze the structure since the register number
+    // won't be pushed any higher.
+    if (SlotNum > Limit)
+      return true;
+
+    SlotNumStr.getAsInteger(10, Result);
+    return false;
+  }
+
+  // we don't expect any other decl type, so fail
+  return true;
+}
+
 void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
   if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
     QualType Ty = VD->getType();
@@ -2419,12 +2536,25 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, 
const ParsedAttr &AL) {
       Diag(SlotLoc, diag::warn_hlsl_deprecated_register_type_i);
       return;
     }
-    StringRef SlotNumStr = Slot.substr(1);
+    const StringRef SlotNumStr = Slot.substr(1);
+
     unsigned N;
-    if (SlotNumStr.getAsInteger(10, N)) {
+
+    // validate that the slot number is a non-empty number
+    if (SlotNumStr.empty() || !llvm::all_of(SlotNumStr, llvm::isDigit)) {
       Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
       return;
     }
+
+    // Validate register number. It should not exceed UINT32_MAX,
+    // including if the resource type is an array that starts
+    // before UINT32_MAX, but ends afterwards.
+    if (ValidateRegisterNumber(SlotNumStr, TheDecl, getASTContext(), RegType,
+                               N)) {
+      Diag(SlotLoc, diag::err_hlsl_register_number_too_large);
+      return;
+    }
+
     SlotNum = N;
   }
 

diff  --git a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl 
b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
index afd7d407c34c2..e2d72197ef602 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
@@ -34,6 +34,21 @@ cbuffer D : register(b 2, space3) {}
 // expected-error@+1 {{expected <numeric_constant>}}
 cbuffer E : register(u-1) {};
 
+// expected-error@+1 {{expected <numeric_constant>}}
+cbuffer F : register(u) {}; 
+
+// expected-error@+1 {{binding type 'u' only applies to UAV resources}}
+cbuffer G : register(u13) {}; 
+
+// expected-error@+1 {{binding type 'c' only applies to numeric variables in 
the global scope}}
+cbuffer H : register(c0) {};
+
+// expected-error@+1  {{binding type 't' only applies to SRV resources}}
+cbuffer I : register(t0) {}
+
+// expected-error@+1  {{binding type 'b' only applies to constant buffer 
resources}}
+tbuffer J : register(b0) {};
+
 // expected-error@+1 {{'register' attribute only applies to cbuffer/tbuffer 
and external global variables}}
 static MyTemplatedSRV<float> U : register(u5);
 

diff  --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl 
b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
new file mode 100644
index 0000000000000..464eb12669a9c
--- /dev/null
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -0,0 +1,90 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - 
-fsyntax-only %s -verify
+
+// test semantic validation for register numbers that exceed UINT32_MAX
+
+struct S {
+  RWBuffer<float> A[4];
+  RWBuffer<int> B[10];
+};
+
+// do some more nesting
+struct S2 {
+  S a[3];
+};
+
+// test that S.A carries the register number over the limit and emits the error
+// expected-error@+1 {{register number should not exceed 4294967295}}
+S s : register(u4294967294); // UINT32_MAX - 1
+
+// test the error is also triggered when analyzing S.B
+// expected-error@+1 {{register number should not exceed 4294967295}}
+S s2 : register(u4294967289);
+
+
+// test the error is also triggered when analyzing S2.a[1].B
+// expected-error@+1 {{register number should not exceed 4294967295}}
+S2 s3 : register(u4294967275);
+
+// expected-error@+1 {{register number should not exceed 4294967295}}
+RWBuffer<float> Buf[10][10] : register(u4294967234);
+
+// test a standard resource array
+// expected-error@+1 {{register number should not exceed 4294967295}}
+RWBuffer<float> Buf2[10] : register(u4294967294); 
+
+// test directly an excessively high register number.
+// expected-error@+1 {{register number should not exceed 4294967295}}
+RWBuffer<float> A : register(u9995294967294);
+
+// test a cbuffer directly with an excessively high register number.
+// expected-error@+1 {{register number should not exceed 4294967295}}
+cbuffer MyCB : register(b9995294967294) {
+  float F[4];
+  int   I[10];
+};
+
+struct MySRV {
+  __hlsl_resource_t [[hlsl::resource_class(SRV)]] x;
+};
+
+struct MySampler {
+  __hlsl_resource_t [[hlsl::resource_class(Sampler)]] x;
+};
+
+struct MyUAV {
+  __hlsl_resource_t [[hlsl::resource_class(UAV)]] x;
+};
+
+// test that 
diff erent resource classes don't contribute to the
+// maximum limit
+struct MyResources {
+  MySRV TheSRV[10]; // t
+  MySampler TheSampler[20]; // s
+  MyUAV TheUAV[40]; // u
+};
+
+// no failures here, since only the SRV contributes to the count,
+// and the count + 10 does not exceed uint32 max.
+MyResources M : register(t4294967284);
+
+// three failures, since each of the three resources exceed the limit
+// expected-error@+3 {{register number should not exceed 4294967295}}
+// expected-error@+2 {{register number should not exceed 4294967295}}
+// expected-error@+1 {{register number should not exceed 4294967295}}
+MyResources M2 : register(t4294967294) : register(s4294967294) : 
register(u4294967294);
+
+// one failure here, just because the final UAV exceeds the limit.
+// expected-error@+1 {{register number should not exceed 4294967295}}
+MyResources M3 : register(t2) : register(s3) : register(u4294967280);
+
+
+// expected-error@+1 {{register number should be an integer}}
+RWBuffer<float> Buf3[10][10] : register(ud); 
+
+// this should work
+RWBuffer<float> GoodBuf : register(u4294967295);
+
+// no errors expected, all 100 register numbers are occupied here
+RWBuffer<float> GoodBufArray[10][10] : register(u4294967194); 
+
+


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to