[clang] [HLSL][Sema] Validate that occupied register numbers never exceed UINT32_MAX (PR #174028)
Joshua Batista via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 6 16:45:56 PST 2026
https://github.com/bob80905 updated https://github.com/llvm/llvm-project/pull/174028
>From 44ff1d1605aefc0dbd197397db9124346dd2411b Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Tue, 30 Dec 2025 13:46:14 -0800
Subject: [PATCH 01/10] first attempt
---
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/lib/Sema/SemaHLSL.cpp | 102 ++++++++++++++++++
...esource_binding_attr_error_uint32_max.hlsl | 31 ++++++
3 files changed, 134 insertions(+)
create mode 100644 clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6c6a26614ad0e..3807d1dab728a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13336,6 +13336,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 UINT32_MAX, 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..7760b1f791374 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2361,6 +2361,99 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
return ValidateMultipleRegisterAnnotations(S, D, RegType);
}
+bool ExceedsUInt32Max(llvm::StringRef S) {
+ constexpr size_t MaxDigits = 10; // UINT32_MAX = 4294967295
+ if (S.size() > MaxDigits)
+ return true;
+
+ if (S.size() < MaxDigits)
+ return false;
+
+ return S.compare("4294967295") > 0;
+}
+
+// return false if the slot count exceeds the limit, true otherwise
+static bool AccumulateHLSLResourceSlots(QualType Ty, llvm::APInt &SlotCount,
+ const llvm::APInt &Limit,
+ ASTContext &Ctx,
+ uint64_t Multiplier = 1) {
+ Ty = Ty.getCanonicalType();
+ const Type *T = Ty.getTypePtr();
+
+ // Early exit if already overflowed
+ if (SlotCount.ugt(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();
+ }
+ // TODO: how do we handle non constant resource arrays?
+
+ QualType ElemTy = AT->getElementType();
+
+ return AccumulateHLSLResourceSlots(ElemTy, SlotCount, Limit, Ctx,
+ Multiplier * Count);
+ }
+
+ // Case 2: resource leaf
+ if (T->isHLSLResourceRecord()) {
+ llvm::APInt Add(SlotCount.getBitWidth(), Multiplier);
+ SlotCount += Add;
+ return SlotCount.ule(Limit);
+ }
+
+ // Case 3: struct / record
+ if (const auto *RT = dyn_cast<RecordType>(T)) {
+ const RecordDecl *RD = RT->getDecl();
+ for (const FieldDecl *Field : RD->fields()) {
+ if (!AccumulateHLSLResourceSlots(Field->getType(), SlotCount, Limit, Ctx,
+ Multiplier))
+ return false;
+ }
+ return true;
+ }
+
+ // Case 4: everything else
+ return true;
+}
+
+// return true if there is something invalid, false otherwise
+bool ValidateRegisterNumber(StringRef SlotNumStr, Decl *TheDecl,
+ ASTContext &Ctx) {
+ if (ExceedsUInt32Max(SlotNumStr))
+ return true;
+
+ llvm::APInt SlotNum;
+ if (SlotNumStr.getAsInteger(10, SlotNum))
+ return false;
+ SlotNum = SlotNum.zext(64);
+
+ // uint32_max isn't 64 bits, but this int should
+ // have a 64 bit width in case it is compared to
+ // another 64 bit-width value. Assert failure otherwise.
+ llvm::APInt Limit(64, UINT32_MAX);
+ VarDecl *VD = dyn_cast<VarDecl>(TheDecl);
+ if (VD) {
+ AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx);
+ return SlotNum.ugt(Limit);
+ }
+ // handle the cbuffer case
+ HLSLBufferDecl *HBD = dyn_cast<HLSLBufferDecl>(TheDecl);
+ if (HBD) {
+ // resources cannot be put within a cbuffer, so no need
+ // to analyze the structure since the register number
+ // won't be pushed any higher.
+ return SlotNum.ugt(Limit);
+ }
+
+ // 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();
@@ -2420,6 +2513,15 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
return;
}
StringRef SlotNumStr = Slot.substr(1);
+
+ // 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())) {
+ Diag(SlotLoc, diag::err_hlsl_register_number_too_large);
+ return;
+ }
+
unsigned N;
if (SlotNumStr.getAsInteger(10, N)) {
Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
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..81a51fb601e54
--- /dev/null
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -0,0 +1,31 @@
+// 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];
+};
+
+// test that S.A carries the register number over the limit and emits the error
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+S s : register(u4294967294); // UINT32_MAX - 1
+
+// test the error is also triggered when analyzing S.B
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+S s2 : register(u4294967289);
+
+// test a standard resource array
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+RWBuffer<float> Buf[10] : register(u4294967294);
+
+// test directly an excessively high register number.
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+RWBuffer<float> A : register(u9995294967294);
+
+// test a struct within a cbuffer
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+cbuffer MyCB : register(b9995294967294) {
+ float F[4];
+ int I[10];
+};
>From 6ffc9ec83357b45c59439a0eaaad5081c64f9d49 Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Tue, 30 Dec 2025 13:57:39 -0800
Subject: [PATCH 02/10] add some more tests
---
...resource_binding_attr_error_uint32_max.hlsl | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 81a51fb601e54..70a226337ef6a 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -7,6 +7,11 @@ struct S {
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 at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
S s : register(u4294967294); // UINT32_MAX - 1
@@ -15,9 +20,17 @@ S s : register(u4294967294); // UINT32_MAX - 1
// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
S s2 : register(u4294967289);
+
+// test the error is also triggered when analyzing S2.a[1].B
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+S2 s3 : register(u4294967275);
+
+// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+RWBuffer<float> Buf[10][10] : register(u4294967234);
+
// test a standard resource array
// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
-RWBuffer<float> Buf[10] : register(u4294967294);
+RWBuffer<float> Buf2[10] : register(u4294967294);
// test directly an excessively high register number.
// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
@@ -29,3 +42,6 @@ cbuffer MyCB : register(b9995294967294) {
float F[4];
int I[10];
};
+
+// no errors expected, all 100 register numbers are occupied here
+RWBuffer<float> Buf3[10][10] : register(u4294967194);
>From 8d838fcfcf86ce2a78ab795e0f9518eeb2b12e5e Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Tue, 30 Dec 2025 14:07:09 -0800
Subject: [PATCH 03/10] adjust comment
---
clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 70a226337ef6a..d2e089391143c 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -36,7 +36,7 @@ RWBuffer<float> Buf2[10] : register(u4294967294);
// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
RWBuffer<float> A : register(u9995294967294);
-// test a struct within a cbuffer
+// test a cbuffer directly with an excessively high register number.
// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
cbuffer MyCB : register(b9995294967294) {
float F[4];
>From 8968c821e246667c24c8d7430aa89e54bb3c8dfc Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Wed, 31 Dec 2025 00:39:59 -0800
Subject: [PATCH 04/10] address Damyan
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 +-
clang/lib/Sema/SemaHLSL.cpp | 59 +++++++------------
...esource_binding_attr_error_uint32_max.hlsl | 20 ++++---
3 files changed, 35 insertions(+), 46 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3807d1dab728a..38205d6087f68 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13336,7 +13336,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 UINT32_MAX, 4294967295">;
+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 7760b1f791374..157c6c0f6def7 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2361,27 +2361,15 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
return ValidateMultipleRegisterAnnotations(S, D, RegType);
}
-bool ExceedsUInt32Max(llvm::StringRef S) {
- constexpr size_t MaxDigits = 10; // UINT32_MAX = 4294967295
- if (S.size() > MaxDigits)
- return true;
-
- if (S.size() < MaxDigits)
- return false;
-
- return S.compare("4294967295") > 0;
-}
-
// return false if the slot count exceeds the limit, true otherwise
-static bool AccumulateHLSLResourceSlots(QualType Ty, llvm::APInt &SlotCount,
- const llvm::APInt &Limit,
- ASTContext &Ctx,
+static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
+ const uint64_t &Limit, ASTContext &Ctx,
uint64_t Multiplier = 1) {
Ty = Ty.getCanonicalType();
const Type *T = Ty.getTypePtr();
// Early exit if already overflowed
- if (SlotCount.ugt(Limit))
+ if (SlotCount > Limit)
return false;
// Case 1: array type
@@ -2391,19 +2379,17 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, llvm::APInt &SlotCount,
if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
Count = CAT->getSize().getZExtValue();
}
- // TODO: how do we handle non constant resource arrays?
QualType ElemTy = AT->getElementType();
-
return AccumulateHLSLResourceSlots(ElemTy, SlotCount, Limit, Ctx,
Multiplier * Count);
}
// Case 2: resource leaf
if (T->isHLSLResourceRecord()) {
- llvm::APInt Add(SlotCount.getBitWidth(), Multiplier);
- SlotCount += Add;
- return SlotCount.ule(Limit);
+
+ SlotCount += Multiplier;
+ return SlotCount <= Limit;
}
// Case 3: struct / record
@@ -2422,24 +2408,17 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, llvm::APInt &SlotCount,
}
// return true if there is something invalid, false otherwise
-bool ValidateRegisterNumber(StringRef SlotNumStr, Decl *TheDecl,
- ASTContext &Ctx) {
- if (ExceedsUInt32Max(SlotNumStr))
- return true;
-
- llvm::APInt SlotNum;
+static bool ValidateRegisterNumber(StringRef SlotNumStr, Decl *TheDecl,
+ ASTContext &Ctx) {
+ uint64_t SlotNum;
if (SlotNumStr.getAsInteger(10, SlotNum))
return false;
- SlotNum = SlotNum.zext(64);
- // uint32_max isn't 64 bits, but this int should
- // have a 64 bit width in case it is compared to
- // another 64 bit-width value. Assert failure otherwise.
- llvm::APInt Limit(64, UINT32_MAX);
+ uint64_t Limit = UINT32_MAX;
VarDecl *VD = dyn_cast<VarDecl>(TheDecl);
if (VD) {
AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx);
- return SlotNum.ugt(Limit);
+ return SlotNum > Limit;
}
// handle the cbuffer case
HLSLBufferDecl *HBD = dyn_cast<HLSLBufferDecl>(TheDecl);
@@ -2447,7 +2426,7 @@ bool ValidateRegisterNumber(StringRef SlotNumStr, Decl *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.
- return SlotNum.ugt(Limit);
+ return SlotNum > Limit;
}
// we don't expect any other decl type, so fail
@@ -2514,6 +2493,13 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
}
StringRef SlotNumStr = Slot.substr(1);
+ unsigned N;
+ // validate that the stringref has 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.
@@ -2522,11 +2508,8 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
return;
}
- unsigned N;
- if (SlotNumStr.getAsInteger(10, N)) {
- Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
- return;
- }
+ // this shouldn't fail now that it's validated.
+ assert(!SlotNumStr.getAsInteger(10, N));
SlotNum = N;
}
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index d2e089391143c..9f2f4b0a7d816 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -13,31 +13,31 @@ struct S2 {
};
// test that S.A carries the register number over the limit and emits the error
-// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +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 at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +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 at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +1 {{register number should not exceed 4294967295}}
S2 s3 : register(u4294967275);
-// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +1 {{register number should not exceed 4294967295}}
RWBuffer<float> Buf[10][10] : register(u4294967234);
// test a standard resource array
-// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +1 {{register number should not exceed 4294967295}}
RWBuffer<float> Buf2[10] : register(u4294967294);
// test directly an excessively high register number.
-// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +1 {{register number should not exceed 4294967295}}
RWBuffer<float> A : register(u9995294967294);
// test a cbuffer directly with an excessively high register number.
-// expected-error at +1 {{register number should not exceed UINT32_MAX, 4294967295}}
+// expected-error at +1 {{register number should not exceed 4294967295}}
cbuffer MyCB : register(b9995294967294) {
float F[4];
int I[10];
@@ -45,3 +45,9 @@ cbuffer MyCB : register(b9995294967294) {
// no errors expected, all 100 register numbers are occupied here
RWBuffer<float> Buf3[10][10] : register(u4294967194);
+
+// expected-error at +1 {{register number should be an integer}}
+RWBuffer<float> Buf4[10][10] : register(ud);
+
+// expected-error at +1 {{expected <numeric_constant>}}
+RWBuffer<float> BadBuf[10][10] : register(u);
>From 34d30832c0f017206de32824324f76ac9a431c83 Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Wed, 31 Dec 2025 10:50:30 -0800
Subject: [PATCH 05/10] try to fix macos issue
---
clang/lib/Sema/SemaHLSL.cpp | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 157c6c0f6def7..207fd7abb827e 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2408,7 +2408,7 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
}
// return true if there is something invalid, false otherwise
-static bool ValidateRegisterNumber(StringRef SlotNumStr, Decl *TheDecl,
+static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
ASTContext &Ctx) {
uint64_t SlotNum;
if (SlotNumStr.getAsInteger(10, SlotNum))
@@ -2491,7 +2491,7 @@ 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;
// validate that the stringref has a non-empty number
@@ -2508,8 +2508,10 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
return;
}
- // this shouldn't fail now that it's validated.
- assert(!SlotNumStr.getAsInteger(10, N));
+ if (SlotNumStr.getAsInteger(10, N)) {
+ Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
+ return;
+ }
SlotNum = N;
}
>From 05d31181e14f3f6e18034b5e49230a4c3a7efc3f Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Wed, 31 Dec 2025 12:33:58 -0800
Subject: [PATCH 06/10] address Finn
---
clang/lib/Sema/SemaHLSL.cpp | 34 +++++++++++--------
...esource_binding_attr_error_uint32_max.hlsl | 7 ++--
2 files changed, 23 insertions(+), 18 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 207fd7abb827e..e30f5b4e5d8dc 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2376,9 +2376,8 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
if (const auto *AT = dyn_cast<ArrayType>(T)) {
uint64_t Count = 1;
- if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
Count = CAT->getSize().getZExtValue();
- }
QualType ElemTy = AT->getElementType();
return AccumulateHLSLResourceSlots(ElemTy, SlotCount, Limit, Ctx,
@@ -2395,11 +2394,11 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
// Case 3: struct / record
if (const auto *RT = dyn_cast<RecordType>(T)) {
const RecordDecl *RD = RT->getDecl();
- for (const FieldDecl *Field : RD->fields()) {
+ for (const FieldDecl *Field : RD->fields())
if (!AccumulateHLSLResourceSlots(Field->getType(), SlotCount, Limit, Ctx,
Multiplier))
return false;
- }
+
return true;
}
@@ -2409,24 +2408,32 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
// return true if there is something invalid, false otherwise
static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
- ASTContext &Ctx) {
+ ASTContext &Ctx, unsigned &Result) {
uint64_t SlotNum;
if (SlotNumStr.getAsInteger(10, SlotNum))
return false;
uint64_t Limit = UINT32_MAX;
- VarDecl *VD = dyn_cast<VarDecl>(TheDecl);
- if (VD) {
+ if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx);
- return SlotNum > Limit;
+
+ bool TooHigh = SlotNum > Limit;
+ if (!TooHigh)
+ SlotNumStr.getAsInteger(10, Result);
+ return TooHigh;
}
// handle the cbuffer case
HLSLBufferDecl *HBD = dyn_cast<HLSLBufferDecl>(TheDecl);
if (HBD) {
+ SlotNumStr.getAsInteger(10, Result);
// resources cannot be put within a cbuffer, so no need
// to analyze the structure since the register number
// won't be pushed any higher.
- return SlotNum > Limit;
+ bool TooHigh = SlotNum > Limit;
+ if (!TooHigh)
+ SlotNumStr.getAsInteger(10, Result);
+
+ return TooHigh;
}
// we don't expect any other decl type, so fail
@@ -2494,7 +2501,8 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
const StringRef SlotNumStr = Slot.substr(1);
unsigned N;
- // validate that the stringref has a non-empty number
+
+ // 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;
@@ -2503,15 +2511,11 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
// 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())) {
+ if (ValidateRegisterNumber(SlotNumStr, TheDecl, getASTContext(), N)) {
Diag(SlotLoc, diag::err_hlsl_register_number_too_large);
return;
}
- if (SlotNumStr.getAsInteger(10, N)) {
- Diag(SlotLoc, diag::err_hlsl_unsupported_register_number);
- return;
- }
SlotNum = N;
}
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 9f2f4b0a7d816..7ed084aec2cfd 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -2,6 +2,10 @@
// test semantic validation for register numbers that exceed UINT32_MAX
+
+// expected-error at +1 {{expected <numeric_constant>}}
+RWBuffer<float> BadBuf[10][10] : register(u);
+
struct S {
RWBuffer<float> A[4];
RWBuffer<int> B[10];
@@ -48,6 +52,3 @@ RWBuffer<float> Buf3[10][10] : register(u4294967194);
// expected-error at +1 {{register number should be an integer}}
RWBuffer<float> Buf4[10][10] : register(ud);
-
-// expected-error at +1 {{expected <numeric_constant>}}
-RWBuffer<float> BadBuf[10][10] : register(u);
>From d7150e90fe2219528f3a98fbd88b4f0a7ee1b961 Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Wed, 31 Dec 2025 12:45:53 -0800
Subject: [PATCH 07/10] one quick move
---
clang/test/SemaHLSL/resource_binding_attr_error.hlsl | 3 +++
.../test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl | 4 ----
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
index afd7d407c34c2..66b42825c5cf7 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl
@@ -34,6 +34,9 @@ cbuffer D : register(b 2, space3) {}
// expected-error at +1 {{expected <numeric_constant>}}
cbuffer E : register(u-1) {};
+// expected-error at +1 {{expected <numeric_constant>}}
+cbuffer F : register(u) {};
+
// expected-error at +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
index 7ed084aec2cfd..6f35aed0f2ccb 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -2,10 +2,6 @@
// test semantic validation for register numbers that exceed UINT32_MAX
-
-// expected-error at +1 {{expected <numeric_constant>}}
-RWBuffer<float> BadBuf[10][10] : register(u);
-
struct S {
RWBuffer<float> A[4];
RWBuffer<int> B[10];
>From 59412b504c4e5753478eee82970e8e861a5b3ee2 Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Wed, 31 Dec 2025 13:29:42 -0800
Subject: [PATCH 08/10] fix off by one error, and add test
---
clang/lib/Sema/SemaHLSL.cpp | 41 +++++++++++--------
...esource_binding_attr_error_uint32_max.hlsl | 12 ++++--
2 files changed, 34 insertions(+), 19 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e30f5b4e5d8dc..db96b49f93a9f 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2386,9 +2386,14 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
// Case 2: resource leaf
if (T->isHLSLResourceRecord()) {
+ // Validate highest slot used
+ uint64_t EndSlot = SlotCount + Multiplier - 1;
+ if (EndSlot > Limit)
+ return false;
- SlotCount += Multiplier;
- return SlotCount <= Limit;
+ // Advance SlotCount past the consumed range
+ SlotCount = EndSlot + 1;
+ return true;
}
// Case 3: struct / record
@@ -2411,29 +2416,33 @@ static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
ASTContext &Ctx, unsigned &Result) {
uint64_t SlotNum;
if (SlotNumStr.getAsInteger(10, SlotNum))
- return false;
+ return true;
- uint64_t Limit = UINT32_MAX;
+ const uint64_t Limit = UINT32_MAX;
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
- AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx);
+ uint64_t BaseSlot = SlotNum;
+
+ if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx))
+ return true;
+
+ // After AccumulateHLSLResourceSlots runs, SlotNum is now
+ // the first free slot; last used was SlotNum - 1
+ if (BaseSlot > Limit)
+ return true;
- bool TooHigh = SlotNum > Limit;
- if (!TooHigh)
- SlotNumStr.getAsInteger(10, Result);
- return TooHigh;
+ SlotNumStr.getAsInteger(10, Result);
+ return false;
}
// handle the cbuffer case
- HLSLBufferDecl *HBD = dyn_cast<HLSLBufferDecl>(TheDecl);
- if (HBD) {
- SlotNumStr.getAsInteger(10, Result);
+ 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.
- bool TooHigh = SlotNum > Limit;
- if (!TooHigh)
- SlotNumStr.getAsInteger(10, Result);
+ if (SlotNum > Limit)
+ return true;
- return TooHigh;
+ SlotNumStr.getAsInteger(10, Result);
+ return false;
}
// we don't expect any other decl type, so fail
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 6f35aed0f2ccb..780d35aa0b346 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -43,8 +43,14 @@ cbuffer MyCB : register(b9995294967294) {
int I[10];
};
-// no errors expected, all 100 register numbers are occupied here
-RWBuffer<float> Buf3[10][10] : register(u4294967194);
// expected-error at +1 {{register number should be an integer}}
-RWBuffer<float> Buf4[10][10] : register(ud);
+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);
+
+
>From 2dfdd8702d4604a2d13cd7f5b7cf4b1db438e18c Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Tue, 6 Jan 2026 15:11:06 -0800
Subject: [PATCH 09/10] address tex, fix var names, account for bases, and for
different register classes
---
clang/lib/Sema/SemaHLSL.cpp | 51 +++++++++++++------
...esource_binding_attr_error_uint32_max.hlsl | 34 +++++++++++++
2 files changed, 70 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index db96b49f93a9f..90fa4da9b612e 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2362,14 +2362,16 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc,
}
// return false if the slot count exceeds the limit, true otherwise
-static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
- const uint64_t &Limit, ASTContext &Ctx,
- uint64_t Multiplier = 1) {
+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 (SlotCount > Limit)
+ if (StartSlot > Limit)
return false;
// Case 1: array type
@@ -2380,29 +2382,44 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
Count = CAT->getSize().getZExtValue();
QualType ElemTy = AT->getElementType();
- return AccumulateHLSLResourceSlots(ElemTy, SlotCount, Limit, Ctx,
- Multiplier * Count);
+ return AccumulateHLSLResourceSlots(ElemTy, StartSlot, Limit, ResClass, Ctx,
+ ArrayCount * Count);
}
// Case 2: resource leaf
- if (T->isHLSLResourceRecord()) {
+ 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 = SlotCount + Multiplier - 1;
+ uint64_t EndSlot = StartSlot + ArrayCount - 1;
if (EndSlot > Limit)
return false;
// Advance SlotCount past the consumed range
- SlotCount = EndSlot + 1;
+ StartSlot = EndSlot + 1;
return true;
}
// Case 3: struct / record
if (const auto *RT = dyn_cast<RecordType>(T)) {
const RecordDecl *RD = RT->getDecl();
- for (const FieldDecl *Field : RD->fields())
- if (!AccumulateHLSLResourceSlots(Field->getType(), SlotCount, Limit, Ctx,
- Multiplier))
+
+ 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;
}
@@ -2413,16 +2430,19 @@ static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &SlotCount,
// return true if there is something invalid, false otherwise
static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
- ASTContext &Ctx, unsigned &Result) {
+ ASTContext &Ctx, RegisterType RegTy,
+ unsigned &Result) {
uint64_t SlotNum;
if (SlotNumStr.getAsInteger(10, SlotNum))
return true;
const uint64_t Limit = UINT32_MAX;
+
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
uint64_t BaseSlot = SlotNum;
- if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, Ctx))
+ if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit,
+ getResourceClass(RegTy), Ctx))
return true;
// After AccumulateHLSLResourceSlots runs, SlotNum is now
@@ -2520,7 +2540,8 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) {
// 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(), N)) {
+ if (ValidateRegisterNumber(SlotNumStr, TheDecl, getASTContext(), RegType,
+ N)) {
Diag(SlotLoc, diag::err_hlsl_register_number_too_large);
return;
}
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 780d35aa0b346..61c21c3916afb 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -43,6 +43,40 @@ cbuffer MyCB : register(b9995294967294) {
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 different 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 at +3 {{register number should not exceed 4294967295}}
+// expected-error at +2 {{register number should not exceed 4294967295}}
+// expected-error at +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 at +1 {{register number should not exceed 4294967295}}
+MyResources M3 : register(t2) : register(s3) : register(u4294967280);
+
// expected-error at +1 {{register number should be an integer}}
RWBuffer<float> Buf3[10][10] : register(ud);
>From 05ec80f46cc6185f4672946f68144ab2f7d684a5 Mon Sep 17 00:00:00 2001
From: Joshua Batista <jbatista at microsoft.com>
Date: Tue, 6 Jan 2026 16:45:00 -0800
Subject: [PATCH 10/10] fix bug with c and i register types
---
clang/lib/Sema/SemaHLSL.cpp | 7 +++++++
.../SemaHLSL/resource_binding_attr_error_uint32_max.hlsl | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 90fa4da9b612e..ff836d50534c6 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2437,6 +2437,13 @@ static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl,
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)
+ return false;
if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) {
uint64_t BaseSlot = SlotNum;
diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
index 61c21c3916afb..464eb12669a9c 100644
--- a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
+++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl
@@ -61,7 +61,7 @@ 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.
More information about the cfe-commits
mailing list