[clang] [HLSL] Enable conversion of ConstantBuffer<T> to T (PR #205996)
Helena Kotas via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 26 10:16:24 PDT 2026
https://github.com/hekota updated https://github.com/llvm/llvm-project/pull/205996
>From 8e8d54219ed36bc352fd3a0b57919722e4fe2851 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Fri, 26 Jun 2026 00:05:44 -0700
Subject: [PATCH 1/2] [HLSL] Enable conversion of ConstantBuffer<T> to T
---
clang/include/clang/Sema/Initialization.h | 6 +-
clang/include/clang/Sema/SemaHLSL.h | 3 +-
clang/lib/Sema/SemaExpr.cpp | 18 +-
clang/lib/Sema/SemaExprMember.cpp | 2 +-
clang/lib/Sema/SemaHLSL.cpp | 8 +-
clang/lib/Sema/SemaInit.cpp | 30 ++-
.../AST/HLSL/ConstantBuffers-AST-error.hlsl | 24 --
clang/test/AST/HLSL/ConstantBuffers-AST.hlsl | 31 +++
.../test/CodeGenHLSL/cbuffer_copy_layout.hlsl | 23 --
.../ConstantBufferT-struct-passing.hlsl | 255 ++++++++++++++++++
.../SemaHLSL/Resources/ConstantBuffers.hlsl | 3 +-
11 files changed, 342 insertions(+), 61 deletions(-)
delete mode 100644 clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
delete mode 100644 clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/ConstantBufferT-struct-passing.hlsl
diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h
index 96ea4fae4437c..2c2df1f03747b 100644
--- a/clang/include/clang/Sema/Initialization.h
+++ b/clang/include/clang/Sema/Initialization.h
@@ -979,7 +979,9 @@ class InitializationSequence {
/// Initialize an aggreagate with parenthesized list of values.
/// This is a C++20 feature.
- SK_ParenthesizedListInit
+ SK_ParenthesizedListInit,
+
+ SK_HLSLBufferConversion
};
/// A single step in the initialization sequence.
@@ -1434,6 +1436,8 @@ class InitializationSequence {
/// single element and rewrap it at the end.
void RewrapReferenceInitList(QualType T, InitListExpr *Syntactic);
+ void AddHLSLBufferConversionStep(QualType T);
+
/// Note that this initialization sequence failed.
void SetFailed(FailureKind Failure) {
SequenceKind = FailedSequence;
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 4a95cb8b2d181..8928524e49783 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -148,8 +148,7 @@ class SemaHLSL : public SemaBase {
// Returns the result of converting ConstantBuffer<T> to
// `const hlsl_constant T&`. If `BaseExpr`'s type is not ConstantBuffer<T>
// then the return value is `std::nullopt`.
- std::optional<ExprResult>
- tryPerformConstantBufferConversion(ExprResult &BaseExpr);
+ std::optional<ExprResult> tryPerformConstantBufferConversion(Expr *BaseExpr);
// Returns the conversion operator to convert `RD` to `const hlsl_constant
// Type&`. Returns `nullptr` if it could not be found.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 7c868d176e803..1fa42a0093fe2 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16135,10 +16135,20 @@ ExprResult Sema::BuildBinOp(Scope *S, SourceLocation OpLoc,
RHSExpr = resolvedRHS.get();
}
- if (getLangOpts().HLSL && (LHSExpr->getType()->isHLSLResourceRecord() ||
- LHSExpr->getType()->isHLSLResourceRecordArray())) {
- if (!HLSL().CheckResourceBinOp(Opc, LHSExpr, RHSExpr, OpLoc))
- return ExprError();
+ if (getLangOpts().HLSL) {
+ if (LHSExpr->getType()->isHLSLResourceRecord() ||
+ LHSExpr->getType()->isHLSLResourceRecordArray()) {
+ if (!HLSL().CheckResourceBinOp(Opc, LHSExpr, RHSExpr, OpLoc))
+ return ExprError();
+ } else if (RHSExpr->getType()->isHLSLResourceRecord()) {
+ std::optional<ExprResult> ConvRHS =
+ HLSL().tryPerformConstantBufferConversion(RHSExpr);
+ if (ConvRHS && Context.hasSameUnqualifiedType(
+ LHSExpr->getType(), ConvRHS->get()->getType())) {
+ assert(!ConvRHS->isInvalid());
+ RHSExpr = ConvRHS->get();
+ }
+ }
}
if (getLangOpts().CPlusPlus) {
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 851d58c49f7b9..62ecc52c0374b 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1298,7 +1298,7 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
// to access the member.
if (S.getLangOpts().HLSL && BaseType->isHLSLResourceRecord()) {
if (std::optional<ExprResult> ConvBase =
- S.HLSL().tryPerformConstantBufferConversion(BaseExpr)) {
+ S.HLSL().tryPerformConstantBufferConversion(BaseExpr.get())) {
assert(!ConvBase->isInvalid());
BaseExpr = *ConvBase;
BaseType = BaseExpr.get()->getType();
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 075dc97b0aef2..1d4deedcbbf7d 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -3279,8 +3279,8 @@ NamedDecl *SemaHLSL::getConstantBufferConversionFunction(QualType Type,
}
std::optional<ExprResult>
-SemaHLSL::tryPerformConstantBufferConversion(ExprResult &BaseExpr) {
- QualType BaseType = BaseExpr.get()->getType();
+SemaHLSL::tryPerformConstantBufferConversion(Expr *BaseExpr) {
+ QualType BaseType = BaseExpr->getType();
const HLSLAttributedResourceType *ResTy =
HLSLAttributedResourceType::findHandleTypeOnResource(
BaseType.getTypePtr());
@@ -3297,7 +3297,7 @@ SemaHLSL::tryPerformConstantBufferConversion(ExprResult &BaseExpr) {
auto *ConversionDecl =
cast<CXXConversionDecl>(NamedConversionDecl->getUnderlyingDecl());
- return SemaRef.BuildCXXMemberCallExpr(BaseExpr.get(), NamedConversionDecl,
+ return SemaRef.BuildCXXMemberCallExpr(BaseExpr, NamedConversionDecl,
ConversionDecl,
/*HadMultipleCandidates=*/false);
}
@@ -5146,7 +5146,7 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
// Writebacks are performed with `=` binary operator, which allows for
// overload resolution on writeback result expressions.
- Res = SemaRef.ActOnBinOp(SemaRef.getCurScope(), Param->getBeginLoc(),
+ Res = SemaRef.ActOnBinOp(SemaRef.getCurScope(), Arg->getBeginLoc(),
tok::equal, ArgOpV, OpV);
if (Res.isInvalid())
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 8f685feac4beb..4f549f3eca108 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -3977,6 +3977,7 @@ void InitializationSequence::Step::Destroy() {
case SK_OCLSamplerInit:
case SK_OCLZeroOpaqueType:
case SK_ParenthesizedListInit:
+ case SK_HLSLBufferConversion:
break;
case SK_ConversionSequence:
@@ -4306,6 +4307,13 @@ void InitializationSequence::RewrapReferenceInitList(QualType T,
Steps.push_back(S);
}
+void InitializationSequence::AddHLSLBufferConversionStep(QualType T) {
+ Step S;
+ S.Kind = SK_HLSLBufferConversion;
+ S.Type = T;
+ Steps.push_back(S);
+}
+
void InitializationSequence::SetOverloadFailure(FailureKind Failure,
OverloadingResult Result) {
setSequenceKind(FailedSequence);
@@ -6349,6 +6357,13 @@ static void TryUserDefinedConversion(Sema &S,
HadMultipleCandidates);
if (ConvType->isRecordType()) {
+ if (S.getLangOpts().HLSL &&
+ ConvType.getAddressSpace() == LangAS::hlsl_constant &&
+ S.Context.hasSameUnqualifiedType(ConvType, DestType)) {
+ Sequence.AddHLSLBufferConversionStep(ConvType);
+ return;
+ }
+
// The call is used to direct-initialize [...] the object that is the
// destination of the copy-initialization.
//
@@ -8072,7 +8087,8 @@ ExprResult InitializationSequence::Perform(Sema &S,
case SK_ProduceObjCObject:
case SK_StdInitializerList:
case SK_OCLSamplerInit:
- case SK_OCLZeroOpaqueType: {
+ case SK_OCLZeroOpaqueType:
+ case SK_HLSLBufferConversion: {
assert(Args.size() == 1 || IsHLSLVectorOrMatrixInit);
CurInit = Args[0];
if (!CurInit.get()) return ExprError();
@@ -8859,6 +8875,13 @@ ExprResult InitializationSequence::Perform(Sema &S,
CurInit = S.MaybeBindToTemporary(CurInit.get());
break;
}
+ case SK_HLSLBufferConversion: {
+ CurInit = ImplicitCastExpr::Create(
+ S.Context, Step->Type.getLocalUnqualifiedType(), CK_LValueToRValue,
+ CurInit.get(),
+ /*BasePath=*/nullptr, VK_PRValue, FPOptionsOverride());
+ break;
+ }
}
}
@@ -9866,9 +9889,14 @@ void InitializationSequence::dump(raw_ostream &OS) const {
case SK_OCLZeroOpaqueType:
OS << "OpenCL opaque type from zero";
break;
+
case SK_ParenthesizedListInit:
OS << "initialization from a parenthesized list of values";
break;
+
+ case SK_HLSLBufferConversion:
+ OS << "HLSL buffer conversion";
+ break;
}
OS << " [" << S->Type << ']';
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
deleted file mode 100644
index 4f9d60c741f90..0000000000000
--- a/clang/test/AST/HLSL/ConstantBuffers-AST-error.hlsl
+++ /dev/null
@@ -1,24 +0,0 @@
-// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -ast-dump -finclude-default-header -o - %s 2>&1 | FileCheck %s
-
-// Unimplemented: https://github.com/llvm/llvm-project/issues/195093
-// Once fixed, these tests should work and we should check the AST.
-
-struct S {
- float a;
-};
-ConstantBuffer<S> cb;
-
-void takes_s(S s) {}
-
-void main() {
- S s;
-
- // CHECK: error: no viable constructor copying parameter of type 'const hlsl_constant S'
- takes_s(cb);
-
- // CHECK: error: no viable constructor copying variable of type 'const hlsl_constant S'
- S s2 = cb;
-
- // CHECK: error: assigning to 'S' from incompatible type 'ConstantBuffer<S>'
- s = cb;
-}
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
index 6a880c437db8f..0b51c0fc57543 100644
--- a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
@@ -116,5 +116,36 @@ float main() {
// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue inout
takes_inout_cb(cb);
+ // CHECK: DeclStmt
+ // CHECK-NEXT: VarDecl {{.*}} s2 'S' cinit
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'S' <LValueToRValue>
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl_constant S' lvalue <UserDefinedConversion>
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant S' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant S & {{.*}}
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+ S s2 = cb;
+
+ // CHECK: BinaryOperator {{.*}} 'S' lvalue '='
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} 's' 'S'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'S' <LValueToRValue>
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant S' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant S & {{.*}}
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+ S s;
+ s = cb;
+
+ // CHECK: CallExpr {{.*}} 'void'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(S)' <FunctionToPointerDecay>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'void (S)' lvalue Function {{.*}} 'takes_s' 'void (S)'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'S' <LValueToRValue>
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl_constant S' lvalue <UserDefinedConversion>
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant S' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant S & {{.*}}
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+ takes_s(cb);
+
return f1 + f2 + f3;
}
diff --git a/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl b/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
deleted file mode 100644
index 022844284f4ba..0000000000000
--- a/clang/test/CodeGenHLSL/cbuffer_copy_layout.hlsl
+++ /dev/null
@@ -1,23 +0,0 @@
-// RUN: not %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.3-library %s 2>&1 | FileCheck %s
-
-// Unimplemented: https://github.com/llvm/llvm-project/issues/195093
-// These cases should work. When fixed we should add proper CHECKs.
-
-struct S {
- float3 a;
- float2 b;
-};
-
-cbuffer CB {
- S s_cb;
-}
-
-ConstantBuffer<S> cb;
-
-[numthreads(1,1,1)]
-void main() {
- S l1 = s_cb;
-
- // CHECK: error: no viable constructor copying variable of type 'const hlsl_constant S'
- S l2 = cb;
-}
diff --git a/clang/test/CodeGenHLSL/resources/ConstantBufferT-struct-passing.hlsl b/clang/test/CodeGenHLSL/resources/ConstantBufferT-struct-passing.hlsl
new file mode 100644
index 0000000000000..a8107894757d2
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/ConstantBufferT-struct-passing.hlsl
@@ -0,0 +1,255 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | \
+// RUN: llvm-cxxfilt | FileCheck %s -DCONST_ADDR_SPACE=2 -DPADDING_TYPE="dx.Padding" -check-prefixes=CHECK,CHECK-DXIL
+
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-library -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | \
+// RUN: llvm-cxxfilt | FileCheck %s -DCONST_ADDR_SPACE=12 -DPADDING_TYPE="spirv.Padding" -check-prefixes=CHECK,CHECK-SPIRV
+
+struct P {
+ float a;
+};
+
+struct S : P {
+ double2 b;
+};
+
+struct T {
+ P p;
+ int arr[2];
+};
+
+ConstantBuffer<S> CBS;
+ConstantBuffer<T> CBT;
+
+// CHECK-DXIL: %"class.hlsl::ConstantBuffer" = type { target("dx.CBuffer", %S) }
+// CHECK-SPIRV: %"class.hlsl::ConstantBuffer" = type { target("spirv.VulkanBuffer", %S, 2, 0) }
+// CHECK: %S = type <{ float, target("[[PADDING_TYPE]]", 12), <2 x double> }>
+
+// CHECK-DXIL: %"class.hlsl::ConstantBuffer.0" = type { target("dx.CBuffer", %T) }
+// CHECK-SPIRV: %"class.hlsl::ConstantBuffer.0" = type { target("spirv.VulkanBuffer", %T, 2, 0) }
+// CHECK: %T = type <{ %P, target("[[PADDING_TYPE]]", 12), <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }> }>
+// CHECK: %P = type <{ float }>
+
+// CHECK: %struct.S = type <{ %struct.P, <2 x double> }>
+// CHECK: %struct.P = type { float }
+// CHECK: %struct.T = type { %struct.P, [2 x i32] }
+
+// CHECK: @CBS = internal global %"class.hlsl::ConstantBuffer" poison, align {{(4|8)}}
+// CHECK: @CBT = internal global %"class.hlsl::ConstantBuffer.0" poison, align {{(4|8)}}
+
+void useS(S s) {}
+void useP(P p) {}
+void useT(T t) {}
+
+// CHECK-LABEL: case1
+void case1() {
+// CHECK: %s = alloca %struct.S, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<S>::operator S const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBS)
+
+// s.a
+// CHECK-NEXT: [[CB_A_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[S_A_PTR:%.*]] = getelementptr inbounds %struct.S, ptr %s, i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUFLOAD]], ptr [[S_A_PTR]], align 4
+
+// s.b
+// CHECK-NEXT: [[CB_B_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[S_B_PTR:%.*]] = getelementptr inbounds %struct.S, ptr %s, i32 0, i32 1
+// CHECK-NEXT: [[CBUFLOAD2:%.*]] = load <2 x double>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_B_PTR]], align 8
+// CHECK-NEXT: store <2 x double> [[CBUFLOAD2]], ptr [[S_B_PTR]], align 8
+ S s = CBS;
+}
+
+// CHECK-LABEL: case2
+void case2() {
+// CHECK: %s = alloca %struct.S, align 1
+// CHECK: [[TMP:%.*]] = alloca %struct.S, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<S>::operator S const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBS)
+
+// s.a
+// CHECK-NEXT: [[CB_A_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[S_A_PTR:%.*]] = getelementptr inbounds %struct.S, ptr %s, i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUFLOAD]], ptr [[S_A_PTR]], align 4
+
+// s.b
+// CHECK-NEXT: [[CB_B_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[S_B_PTR:%.*]] = getelementptr inbounds %struct.S, ptr %s, i32 0, i32 1
+// CHECK-NEXT: [[CBUFLOAD2:%.*]] = load <2 x double>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_B_PTR]], align 8
+// CHECK-NEXT: store <2 x double> [[CBUFLOAD2]], ptr [[S_B_PTR]], align 8
+
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i{{(32|64)}}(ptr align 1 [[TMP]], ptr align 1 %s, i{{(32|64)}} 20, i1 false)
+ S s;
+ s = CBS;
+}
+
+// CHECK-LABEL: case3
+void case3() {
+// CHECK: [[TMP:%.*]] = alloca %struct.S, align 1
+// CHECK-NEXT: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<S>::operator S const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBS)
+
+// tmp.a
+// CHECK-NEXT: [[CB_A_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[TMP_A_PTR:%.*]] = getelementptr inbounds %struct.S, ptr [[TMP]], i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUFLOAD]], ptr [[S_A_PTR]], align 4
+
+// tmp.b
+// CHECK-NEXT: [[CB_B_PTR:%.*]] = getelementptr inbounds %S, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[TMP_B_PTR:%.*]] = getelementptr inbounds %struct.S, ptr [[TMP]], i32 0, i32 1
+// CHECK-NEXT: [[CBUFLOAD2:%.*]] = load <2 x double>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_B_PTR]], align 8
+// CHECK-NEXT: store <2 x double> [[CBUFLOAD2]], ptr [[S_B_PTR]], align 8
+
+// CHECK-NEXT: call {{.*}}void @useS(S)(ptr noundef dead_on_return [[TMP]])
+ useS(CBS);
+}
+
+// CHECK-LABEL: case4
+void case4() {
+
+// CHECK: %t = alloca %struct.T, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// t.p
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[T_P_PTR:%.*]] = getelementptr inbounds %struct.T, ptr %t, i32 0, i32 0
+
+// t.p.a
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) %1, i32 0, i32 0
+// CHECK-NEXT: [[T_P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr %2, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD1:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD1]], ptr [[T_P_A_PTR]], align 4
+
+// t.arr
+// CHECK-NEXT: [[CB_ARR_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[T_ARR_PTR:%.*]] = getelementptr inbounds %struct.T, ptr %t, i32 0, i32 1
+
+// t.arr[0]
+// CHECK-NEXT: [[CB_ARR_0_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 0, i32 0, i32 0
+// CHECK-NEXT: [[T_ARR_0_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD2:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_0_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD2]], ptr [[T_ARR_0_PTR]], align 4
+
+// t.arr[1]
+// CHECK-NEXT: [[CB_ARR_1_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[T_ARR_1_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD3:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_1_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD3]], ptr [[T_ARR_1_PTR]], align 4
+
+ T t = CBT;
+}
+
+// CHECK-LABEL: case5
+void case5() {
+// CHECK: %t = alloca %struct.T, align 1
+// CHECK: [[TMP:%.*]] = alloca %struct.T, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// t.p
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[T_P_PTR:%.*]] = getelementptr inbounds %struct.T, ptr %t, i32 0, i32 0
+
+// t.p.a
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) %1, i32 0, i32 0
+// CHECK-NEXT: [[T_P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr %2, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD1:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD1]], ptr [[T_P_A_PTR]], align 4
+
+// t.arr
+// CHECK-NEXT: [[CB_ARR_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[T_ARR_PTR:%.*]] = getelementptr inbounds %struct.T, ptr %t, i32 0, i32 1
+
+// t.arr[0]
+// CHECK-NEXT: [[CB_ARR_0_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 0, i32 0, i32 0
+// CHECK-NEXT: [[T_ARR_0_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD2:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_0_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD2]], ptr [[T_ARR_0_PTR]], align 4
+
+// t.arr[1]
+// CHECK-NEXT: [[CB_ARR_1_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[T_ARR_1_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD3:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_1_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD3]], ptr [[T_ARR_1_PTR]], align 4
+
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i{{(32|64)}}(ptr align 1 [[TMP]], ptr align 1 %t, i{{(32|64)}} 12, i1 false)
+
+ T t;
+ t = CBT;
+}
+
+// CHECK-LABEL: case6
+void case6() {
+// CHECK: [[TMP:%.*]] = alloca %struct.T, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// t.p
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[T_P_PTR:%.*]] = getelementptr inbounds %struct.T, ptr [[TMP]], i32 0, i32 0
+
+// t.p.a
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) %1, i32 0, i32 0
+// CHECK-NEXT: [[T_P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr %2, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD1:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD1]], ptr [[T_P_A_PTR]], align 4
+
+// t.arr
+// CHECK-NEXT: [[CB_ARR_PTR:%.*]] = getelementptr inbounds %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 2
+// CHECK-NEXT: [[T_ARR_PTR:%.*]] = getelementptr inbounds %struct.T, ptr [[TMP]], i32 0, i32 1
+
+// t.arr[0]
+// CHECK-NEXT: [[CB_ARR_0_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 0, i32 0, i32 0
+// CHECK-NEXT: [[T_ARR_0_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD2:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_0_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD2]], ptr [[T_ARR_0_PTR]], align 4
+
+// t.arr[1]
+// CHECK-NEXT: [[CB_ARR_1_PTR:%.*]] = getelementptr inbounds <{ [1 x <{ i32, target("[[PADDING_TYPE]]", 12) }>], i32 }>, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[T_ARR_1_PTR:%.*]] = getelementptr inbounds [2 x i32], ptr [[T_ARR_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD3:%.*]] = load i32, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_ARR_1_PTR]], align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD3]], ptr [[T_ARR_1_PTR]], align 4
+ useT(CBT);
+}
+
+// CHECK-LABEL: case7
+void case7() {
+// CHECK: %p = alloca %struct.P, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds nuw %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr %p, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD]], ptr [[P_A_PTR]], align 4
+ P p = CBT.p;
+}
+
+// CHECK-LABEL: case8
+void case8() {
+// CHECK: %p = alloca %struct.P, align 1
+// CHECK: [[TMP:%.*]] = alloca %struct.P, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds nuw %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr %p, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD]], ptr [[P_A_PTR]], align 4
+
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i{{(32|64)}}(ptr align 1 [[TMP]], ptr align 1 %p, i{{(32|64)}} 4, i1 false)
+ P p;
+ p = CBT.p;
+}
+
+// CHECK-LABEL: case9
+void case9() {
+// CHECK: [[TMP:%.*]] = alloca %struct.P, align 1
+// CHECK: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace([[CONST_ADDR_SPACE]]) @hlsl::ConstantBuffer<T>::operator T const AS[[CONST_ADDR_SPACE]]&() const(ptr {{.*}} @CBT)
+
+// CHECK-NEXT: [[CB_P_PTR:%.*]] = getelementptr inbounds nuw %T, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CB_P_A_PTR:%.*]] = getelementptr inbounds %P, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[P_A_PTR:%.*]] = getelementptr inbounds %struct.P, ptr [[TMP]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD:%.*]] = load float, ptr addrspace([[CONST_ADDR_SPACE]]) [[CB_P_A_PTR]], align 4
+// CHECK-NEXT: store float [[CBUF_LOAD]], ptr [[P_A_PTR]], align 4
+
+// CHECK-NEXT: call {{.*}}void @useP(P)(ptr noundef dead_on_return [[TMP]])
+ useP(CBT.p);
+}
diff --git a/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl b/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
index 6dc566a58b5e4..540fecd6f6c42 100644
--- a/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
+++ b/clang/test/SemaHLSL/Resources/ConstantBuffers.hlsl
@@ -63,7 +63,8 @@ void takes_inout_s(inout S s) {}
void foo() {
// This case should fail because we cannot writeback to `cb` after the call.
- // expected-error at +1 {{no viable constructor copying parameter of type 'const hlsl_constant S'}}
+ // expected-error at +2 {{no viable overloaded '='}}
+ // expected-note@*:* {{candidate function not viable: no known conversion from 'S' to 'const hlsl::ConstantBuffer<S>' for 1st argument}}
takes_inout_s(cb);
}
>From 5e3d5ca13470465ebfbd137290a59993cf13f9e7 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Fri, 26 Jun 2026 10:16:03 -0700
Subject: [PATCH 2/2] update tests - error should point at the call site, not
the called function
---
clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl
index 214cf641f40ed..27d23ddd1688f 100644
--- a/clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl
@@ -36,14 +36,14 @@ void test_asuint_first_arg_const(double D) {
const uint A = 0;
uint B;
asuint(D, A, B);
- // expected-error at hlsl/hlsl_intrinsics.h:* {{read-only variable is not assignable}}
+ // expected-error@*:* {{read-only variable is not assignable}}
}
void test_asuint_second_arg_const(double D) {
const uint A = 0;
uint B;
asuint(D, B, A);
- // expected-error at hlsl/hlsl_intrinsics.h:* {{read-only variable is not assignable}}
+ // expected-error@*:* {{read-only variable is not assignable}}
}
void test_asuint_imidiate_value(double D) {
More information about the cfe-commits
mailing list