[clang] [HLSL] Ignore complex types that do not contribute to cbuffer layout (PR #184276)
Helena Kotas via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 10 15:46:16 PDT 2026
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/184276
>From a90196f4105de8c6168fc37ffdd80a37b5d285fe Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 2 Mar 2026 17:06:02 -0800
Subject: [PATCH 1/2] [HLSL] Ignore complex types that do not contribute to
cbuffer layout
Fixes #183788
---
clang/lib/CodeGen/CGHLSLRuntime.cpp | 6 ---
clang/lib/Sema/SemaHLSL.cpp | 48 ++++++++++++++-----
.../resources/cbuffer-empty-struct-array.hlsl | 41 ++++++++++++++++
3 files changed, 78 insertions(+), 17 deletions(-)
create mode 100644 clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 79d709867dc02..e518a7094077f 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -322,12 +322,6 @@ void CGHLSLRuntime::emitBufferGlobalsAndMetadata(
// Emit static and groupshared variables and resource classes inside
// cbuffer as regular globals
CGM.EmitGlobal(VD);
- } else {
- // Anything else that is not in the hlsl_constant address space must be
- // an empty struct or a zero-sized array and can be ignored
- assert(BufDecl->getASTContext().getTypeSize(VDTy) == 0 &&
- "constant buffer decl with non-zero sized type outside of "
- "hlsl_constant address space");
}
continue;
}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 911dba40d3bde..a57cd622a4956 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -452,6 +452,31 @@ static IdentifierInfo *getHostLayoutStructName(Sema &S, NamedDecl *BaseDecl,
};
}
+static const Type *createHostLayoutType(Sema &S, const Type *Ty) {
+ ASTContext &AST = S.getASTContext();
+ if (auto *RD = Ty->getAsCXXRecordDecl()) {
+ if (!requiresImplicitBufferLayoutStructure(RD))
+ return Ty;
+ RD = createHostLayoutStruct(S, RD);
+ if (!RD)
+ return nullptr;
+ return AST.getCanonicalTagType(RD)->getTypePtr();
+ }
+
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(Ty)) {
+ const Type *ElementTy = createHostLayoutType(
+ S, CAT->getElementType()->getUnqualifiedDesugaredType());
+ if (!ElementTy)
+ return nullptr;
+ return AST
+ .getConstantArrayType(QualType(ElementTy, 0), CAT->getSize(), nullptr,
+ CAT->getSizeModifier(),
+ CAT->getIndexTypeCVRQualifiers())
+ .getTypePtr();
+ }
+ return Ty;
+}
+
// Creates a field declaration of given name and type for HLSL buffer layout
// struct. Returns nullptr if the type cannot be use in HLSL Buffer layout.
static FieldDecl *createFieldForHostLayoutStruct(Sema &S, const Type *Ty,
@@ -460,14 +485,9 @@ static FieldDecl *createFieldForHostLayoutStruct(Sema &S, const Type *Ty,
if (isInvalidConstantBufferLeafElementType(Ty))
return nullptr;
- if (auto *RD = Ty->getAsCXXRecordDecl()) {
- if (requiresImplicitBufferLayoutStructure(RD)) {
- RD = createHostLayoutStruct(S, RD);
- if (!RD)
- return nullptr;
- Ty = S.Context.getCanonicalTagType(RD)->getTypePtr();
- }
- }
+ Ty = createHostLayoutType(S, Ty);
+ if (!Ty)
+ return nullptr;
QualType QT = QualType(Ty, 0);
ASTContext &AST = S.getASTContext();
@@ -568,15 +588,21 @@ void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
VD->getType().getAddressSpace() == LangAS::hlsl_groupshared)
continue;
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
+
+ QualType NewTy;
if (FieldDecl *FD =
createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS)) {
// add the field decl to the layout struct
LS->addDecl(FD);
// update address space of the original decl to hlsl_constant
- QualType NewTy =
- AST.getAddrSpaceQualType(VD->getType(), LangAS::hlsl_constant);
- VD->setType(NewTy);
+ NewTy = AST.getAddrSpaceQualType(VD->getType(), LangAS::hlsl_constant);
+ } else {
+ // Declarations in default cbuffer $Globals had the address space set to
+ // hlsl_constant earlier. It needs to be removed now since it does not
+ // have a representable cbuffer layout.
+ NewTy = AST.removeAddrSpaceQualType(VD->getType());
}
+ VD->setType(NewTy);
}
LS->completeDefinition();
BufDecl->addLayoutStruct(LS);
diff --git a/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl b/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
new file mode 100644
index 0000000000000..32ad278b1c42e
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes -o - -DCHECK2 %s | FileCheck %s -check-prefix=CHECK2
+
+// Regression test for issue llvm/llvm-project#183788
+
+// empty struct
+struct A {
+};
+
+// struct with a resource which does not contribute to cbuffer layout
+struct B {
+ RWBuffer<float> Buf;
+};
+
+cbuffer CB {
+ A a[2];
+#ifdef CHECK2
+ int i;
+#endif
+}
+
+B b[2][2];
+#ifdef CHECK2
+int j;
+#endif
+
+[numthreads(4,4,4)]
+void main() {
+}
+
+// CHECK-NOT: @CB.cb = global target("dx.CBuffer", %__cblayout_CB)
+// CHECK-NOT: @A = external hidden addrspace(2) global
+// CHECK-NOT: @B = external hidden addrspace(2) global
+// CHECK-NOT: @"$Globals.cb" = global target("dx.CBuffer",
+
+// CHECK2: @CB.cb = global target("dx.CBuffer", %__cblayout_CB)
+// CHECK2-NOT: @A = external hidden addrspace(2) global
+// CHECK2: @i = external hidden addrspace(2) global i32
+// CHECK2: @"$Globals.cb" = global target("dx.CBuffer",
+// CHECK2-NOT: @B = external hidden addrspace(2) global
+// CHECK2: @j = external hidden addrspace(2) global i32
>From e03855b288b9bf2b88d92592cf3eb26e1bab36b1 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 10 Mar 2026 15:45:56 -0700
Subject: [PATCH 2/2] Check for empty decls in $Globals earlier; do not set
hlsl_constant address space on these
---
clang/lib/Sema/SemaHLSL.cpp | 67 +++++++++++++++----
clang/test/AST/HLSL/ast-dump-SpirvType.hlsl | 8 +--
clang/test/AST/HLSL/pch_spirv_type.hlsl | 2 +-
.../resources/cbuffer-empty-struct-array.hlsl | 32 +++++++--
4 files changed, 84 insertions(+), 25 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index a57cd622a4956..1717756c7cbab 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -571,7 +571,7 @@ static CXXRecordDecl *createHostLayoutStruct(Sema &S,
// - zero-sized arrays
// - non-variable declarations
// The layout struct will be added to the HLSLBufferDecl declarations.
-void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
+static void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
ASTContext &AST = S.getASTContext();
IdentifierInfo *II = getHostLayoutStructName(S, BufDecl, true);
@@ -589,20 +589,27 @@ void createHostLayoutStructForBuffer(Sema &S, HLSLBufferDecl *BufDecl) {
continue;
const Type *Ty = VD->getType()->getUnqualifiedDesugaredType();
- QualType NewTy;
- if (FieldDecl *FD =
- createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS)) {
- // add the field decl to the layout struct
+ FieldDecl *FD =
+ createFieldForHostLayoutStruct(S, Ty, VD->getIdentifier(), LS);
+ // Declarations collected for the default $Globals constant buffer have
+ // already been checked to have non-empty cbuffer layout, so
+ // createFieldForHostLayoutStruct should always succeed. These declarations
+ // already have their address space set to hlsl_constant.
+ // For declarations in a named cbuffer block
+ // createFieldForHostLayoutStruct can still return nullptr if the type
+ // is empty (does not have a cbuffer layout).
+ assert((FD || VD->getType().getAddressSpace() != LangAS::hlsl_constant) &&
+ "host layout field for $Globals decl failed to be created");
+ if (FD) {
+ // Add the field decl to the layout struct.
LS->addDecl(FD);
- // update address space of the original decl to hlsl_constant
- NewTy = AST.getAddrSpaceQualType(VD->getType(), LangAS::hlsl_constant);
- } else {
- // Declarations in default cbuffer $Globals had the address space set to
- // hlsl_constant earlier. It needs to be removed now since it does not
- // have a representable cbuffer layout.
- NewTy = AST.removeAddrSpaceQualType(VD->getType());
+ if (VD->getType().getAddressSpace() != LangAS::hlsl_constant) {
+ // Update address space of the original decl to hlsl_constant.
+ QualType NewTy =
+ AST.getAddrSpaceQualType(VD->getType(), LangAS::hlsl_constant);
+ VD->setType(NewTy);
+ }
}
- VD->setType(NewTy);
}
LS->completeDefinition();
BufDecl->addLayoutStruct(LS);
@@ -4405,6 +4412,38 @@ QualType SemaHLSL::getInoutParameterType(QualType Ty) {
return Ty;
}
+// Returns true if the type has a non-empty constant buffer layout (if it is
+// scalar, vector or matrix, or if it contains any of these.
+static bool hasConstantBufferLayout(QualType QT) {
+ const Type *Ty = QT->getUnqualifiedDesugaredType();
+ if (Ty->isScalarType() || Ty->isVectorType() || Ty->isMatrixType())
+ return true;
+
+ if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray())
+ return false;
+
+ if (const auto *RD = Ty->getAsCXXRecordDecl()) {
+ for (const auto *FD : RD->fields()) {
+ if (hasConstantBufferLayout(FD->getType()))
+ return true;
+ }
+ assert(RD->getNumBases() <= 1 &&
+ "HLSL doesn't support multiple inheritance");
+ return RD->getNumBases()
+ ? hasConstantBufferLayout(RD->bases_begin()->getType())
+ : false;
+ }
+
+ if (const auto *AT = dyn_cast<ArrayType>(Ty)) {
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+ if (isZeroSizedArray(CAT))
+ return false;
+ return hasConstantBufferLayout(AT->getElementType());
+ }
+
+ return false;
+}
+
static bool IsDefaultBufferConstantDecl(const ASTContext &Ctx, VarDecl *VD) {
bool IsVulkan =
Ctx.getTargetInfo().getTriple().getOS() == llvm::Triple::Vulkan;
@@ -4414,7 +4453,7 @@ static bool IsDefaultBufferConstantDecl(const ASTContext &Ctx, VarDecl *VD) {
QT.getAddressSpace() == LangAS::Default &&
VD->getStorageClass() != SC_Static &&
!VD->hasAttr<HLSLVkConstantIdAttr>() && !IsVKPushConstant &&
- !isInvalidConstantBufferLeafElementType(QT.getTypePtr());
+ hasConstantBufferLayout(QT);
}
void SemaHLSL::deduceAddressSpace(VarDecl *Decl) {
diff --git a/clang/test/AST/HLSL/ast-dump-SpirvType.hlsl b/clang/test/AST/HLSL/ast-dump-SpirvType.hlsl
index a4a147622c6cc..e6ea31341a3cc 100644
--- a/clang/test/AST/HLSL/ast-dump-SpirvType.hlsl
+++ b/clang/test/AST/HLSL/ast-dump-SpirvType.hlsl
@@ -5,19 +5,19 @@ typedef vk::SpirvOpaqueType<123, RWBuffer<float>, vk::integral_constant<uint, 4>
// CHECK: TypedefDecl 0x{{.+}} <{{.+}}:6:1, col:133> col:133 referenced BType 'vk::SpirvType<12, 2, 4, vk::integral_constant<uint64_t, 4886718345L>, float, vk::Literal<vk::integral_constant<uint, 456>>>':'__hlsl_spirv_type<12, 2, 4, vk::integral_constant<unsigned long, 4886718345>, float, vk::Literal<vk::integral_constant<uint, 456>>>'
typedef vk::SpirvType<12, 2, 4, vk::integral_constant<uint64_t, 0x123456789>, float, vk::Literal<vk::integral_constant<uint, 456>>> BType;
-// CHECK: VarDecl 0x{{.+}} <{{.+}}:9:1, col:7> col:7 AValue 'hlsl_constant AType':'hlsl_constant __hlsl_spirv_type<123, 0, 0, RWBuffer<float>, vk::integral_constant<unsigned int, 4>>'
+// CHECK: VarDecl 0x{{.+}} <{{.+}}:9:1, col:7> col:7 AValue 'AType':'__hlsl_spirv_type<123, 0, 0, RWBuffer<float>, vk::integral_constant<unsigned int, 4>>'
AType AValue;
-// CHECK: VarDecl 0x{{.+}} <{{.+}}:11:1, col:7> col:7 BValue 'hlsl_constant BType':'hlsl_constant __hlsl_spirv_type<12, 2, 4, vk::integral_constant<unsigned long, 4886718345>, float, vk::Literal<vk::integral_constant<uint, 456>>>'
+// CHECK: VarDecl 0x{{.+}} <{{.+}}:11:1, col:7> col:7 BValue 'BType':'__hlsl_spirv_type<12, 2, 4, vk::integral_constant<unsigned long, 4886718345>, float, vk::Literal<vk::integral_constant<uint, 456>>>'
BType BValue;
-// CHECK: VarDecl 0x{{.+}} <{{.+}}:14:1, col:80> col:80 CValue 'hlsl_constant vk::SpirvOpaqueType<123, vk::Literal<vk::integral_constant<uint, 305419896>>>':'hlsl_constant __hlsl_spirv_type<123, 0, 0, vk::Literal<vk::integral_constant<uint, 305419896>>>'
+// CHECK: VarDecl 0x{{.+}} <{{.+}}:14:1, col:80> col:80 CValue 'vk::SpirvOpaqueType<123, vk::Literal<vk::integral_constant<uint, 305419896>>>':'__hlsl_spirv_type<123, 0, 0, vk::Literal<vk::integral_constant<uint, 305419896>>>'
vk::SpirvOpaqueType<123, vk::Literal<vk::integral_constant<uint, 0x12345678>>> CValue;
// CHECK: TypeAliasDecl 0x{{.+}} <{{.+}}:18:1, col:72> col:7 Array 'vk::SpirvOpaqueType<28, T, vk::integral_constant<uint, L>>':'__hlsl_spirv_type<28U, 0, 0, T, vk::integral_constant<uint, L>>'
template <class T, uint L>
using Array = vk::SpirvOpaqueType<28, T, vk::integral_constant<uint, L>>;
-// CHECK: VarDecl 0x{{.+}} <{{.+}}:21:1, col:16> col:16 DValue 'hlsl_constant Array<uint, 5>':'hlsl_constant __hlsl_spirv_type<28, 0, 0, uint, vk::integral_constant<unsigned int, 5>>'
+// CHECK: VarDecl 0x{{.+}} <{{.+}}:21:1, col:16> col:16 DValue 'Array<uint, 5>':'__hlsl_spirv_type<28, 0, 0, uint, vk::integral_constant<unsigned int, 5>>'
Array<uint, 5> DValue;
[numthreads(1, 1, 1)]
diff --git a/clang/test/AST/HLSL/pch_spirv_type.hlsl b/clang/test/AST/HLSL/pch_spirv_type.hlsl
index 045f89a1b8461..5b34956d0db1c 100644
--- a/clang/test/AST/HLSL/pch_spirv_type.hlsl
+++ b/clang/test/AST/HLSL/pch_spirv_type.hlsl
@@ -6,7 +6,7 @@
// Make sure PCH works by using function declared in PCH header and declare a SpirvType in current file.
// CHECK:FunctionDecl 0x[[FOO:[0-9a-f]+]] <{{.*}}:2:1, line:4:1> line:2:8 imported used foo 'float2 (float2, float2)'
-// CHECK:VarDecl 0x{{[0-9a-f]+}} <{{.*}}:10:1, col:92> col:92 buffers2 'hlsl_constant vk::SpirvOpaqueType<28, RWBuffer<float>, vk::integral_constant<uint, 4>>':'hlsl_constant __hlsl_spirv_type<28, 0, 0, RWBuffer<float>, vk::integral_constant<unsigned int, 4>>'
+// CHECK:VarDecl 0x{{[0-9a-f]+}} <{{.*}}:10:1, col:92> col:92 buffers2 'vk::SpirvOpaqueType<28, RWBuffer<float>, vk::integral_constant<uint, 4>>':'__hlsl_spirv_type<28, 0, 0, RWBuffer<float>, vk::integral_constant<unsigned int, 4>>'
vk::SpirvOpaqueType</* OpTypeArray */ 28, RWBuffer<float>, vk::integral_constant<uint, 4>> buffers2;
float2 bar(float2 a, float2 b) {
diff --git a/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl b/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
index 32ad278b1c42e..2e92d071202ae 100644
--- a/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
+++ b/clang/test/CodeGenHLSL/resources/cbuffer-empty-struct-array.hlsl
@@ -1,5 +1,7 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes -o - -DCHECK2 %s | FileCheck %s -check-prefix=CHECK2
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes \
+// RUN: -finclude-default-header -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -emit-llvm -disable-llvm-passes \
+// RUN: -finclude-default-header -o - -DCHECK2 %s | FileCheck %s -check-prefix=CHECK2
// Regression test for issue llvm/llvm-project#183788
@@ -12,16 +14,28 @@ struct B {
RWBuffer<float> Buf;
};
+struct C : A {
+ B b;
+ RWBuffer<float> Bufs[10];
+ A array[10][2];
+};
+
cbuffer CB {
A a[2];
+ C c;
#ifdef CHECK2
int i;
+ float2 v;
+ int4x4 m;
#endif
}
B b[2][2];
+
#ifdef CHECK2
int j;
+float2 w;
+int4x4 n;
#endif
[numthreads(4,4,4)]
@@ -29,13 +43,19 @@ void main() {
}
// CHECK-NOT: @CB.cb = global target("dx.CBuffer", %__cblayout_CB)
-// CHECK-NOT: @A = external hidden addrspace(2) global
-// CHECK-NOT: @B = external hidden addrspace(2) global
+// CHECK-NOT: @a = external hidden addrspace(2) global
+// CHECK-NOT: @b = external hidden addrspace(2) global
+// CHECK-NOT: @c = external hidden addrspace(2) global
// CHECK-NOT: @"$Globals.cb" = global target("dx.CBuffer",
// CHECK2: @CB.cb = global target("dx.CBuffer", %__cblayout_CB)
-// CHECK2-NOT: @A = external hidden addrspace(2) global
+// CHECK-NOT: @a = external hidden addrspace(2) global
+// CHECK-NOT: @c = external hidden addrspace(2) global
// CHECK2: @i = external hidden addrspace(2) global i32
+// CHECK2: @v = external hidden addrspace(2) global <2 x float>, align 8
+// CHECK2: @m = external hidden addrspace(2) global [4 x <4 x i32>], align 4
// CHECK2: @"$Globals.cb" = global target("dx.CBuffer",
-// CHECK2-NOT: @B = external hidden addrspace(2) global
+// CHECK-NOT: @b = external hidden addrspace(2) global
// CHECK2: @j = external hidden addrspace(2) global i32
+// CHECK2: @w = external hidden addrspace(2) global <2 x float>, align 8
+// CHECK2: @n = external hidden addrspace(2) global [4 x <4 x i32>], align 4
More information about the cfe-commits
mailing list