[clang] [HLSL] Enable invoking a method on a constant buffer struct (PR #206596)
Helena Kotas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 14:55:53 PDT 2026
https://github.com/hekota created https://github.com/llvm/llvm-project/pull/206596
Structs in constant buffers are in the `hlsl_constant` address space and in a specific constant buffer layout. In order to invoke a method on such struct, it first needs to be copied out into temporary variable in a standard layout. This change adds `Lvalue_to_Rvalue` cast to the method `this` argument which takes care of that.
Fixes #190299
>From d07c8452f123eda326f4c6542b733866321b22b3 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Mon, 29 Jun 2026 14:47:46 -0700
Subject: [PATCH] [HLSL] Enable invoking a method on a constant buffer struct
Structs in constant buffers are in the `hlsl_constant` address space
and in a specific constant buffer layout. In order to invoke a method on
such struct, it first needs to be copied out into temporary variable
in a standard layout. This change adds `Lvalue_to_Rvalue` cast to the
method `this` argument which takes care of that.
Fixes #190299
---
clang/lib/Sema/SemaOverload.cpp | 8 ++
clang/test/AST/HLSL/ConstantBuffers-AST.hlsl | 15 +++-
.../resources-in-structs-method-call.hlsl | 36 ++++++++
.../resources-in-structs-method-call.hlsl | 85 +++++++++++++++++++
.../BuiltIns/ConstantBuffer-member-funcs.hlsl | 6 +-
5 files changed, 144 insertions(+), 6 deletions(-)
create mode 100644 clang/test/AST/HLSL/resources-in-structs-method-call.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/resources-in-structs-method-call.hlsl
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c663765573612..c8b05a9f94bd0 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6271,6 +6271,14 @@ ExprResult Sema::PerformImplicitObjectArgumentInitialization(
QualType FromRecordType, DestType;
QualType ImplicitParamRecordType = Method->getFunctionObjectParameterType();
+ if (getLangOpts().HLSL &&
+ From->getType().getAddressSpace() == LangAS::hlsl_constant) {
+ QualType CastType = From->getType().getLocalUnqualifiedType().withConst();
+ From = ImplicitCastExpr::Create(Context, CastType, CK_LValueToRValue, From,
+ /*BasePath=*/nullptr, VK_PRValue,
+ FPOptionsOverride());
+ }
+
Expr::Classification FromClassification;
if (const PointerType *PT = From->getType()->getAs<PointerType>()) {
FromRecordType = PT->getPointeeType();
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
index 6a880c437db8f..327d39a225b0b 100644
--- a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
@@ -71,6 +71,7 @@ ConstantBuffer<S> cb;
struct Nested {
S s;
float b;
+ float getA() const { return s.a; }
};
ConstantBuffer<Nested> cb_nested;
@@ -116,5 +117,17 @@ float main() {
// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue inout
takes_inout_cb(cb);
- return f1 + f2 + f3;
+ // CHECK: VarDecl {{.*}} a 'float'
+ // CHECK-NEXT: ExprWithCleanups {{.*}} 'float'
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'float'
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .getA
+ // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'const Nested' lvalue
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const Nested' <LValueToRValue>
+ // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'const hlsl_constant Nested' lvalue
+ // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator const hlsl_constant Nested &
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<Nested>' lvalue <NoOp>
+ // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
+ float a = cb_nested.getA();
+
+ return f1 + f2 + f3 + a;
}
diff --git a/clang/test/AST/HLSL/resources-in-structs-method-call.hlsl b/clang/test/AST/HLSL/resources-in-structs-method-call.hlsl
new file mode 100644
index 0000000000000..ef8be74671533
--- /dev/null
+++ b/clang/test/AST/HLSL/resources-in-structs-method-call.hlsl
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -ast-dump %s | FileCheck %s
+
+struct MyStruct {
+ float f;
+ RWBuffer<float> Buf;
+
+ void Store() const {
+ Buf[0] = f;
+ }
+};
+
+cbuffer CB {
+ MyStruct one;
+}
+
+MyStruct two;
+
+// CHECK: FunctionDecl {{.*}} main 'void ()'
+[numthreads(1, 1, 1)]
+void main() {
+// CHECK: ExprWithCleanups {{.*}} 'void'
+// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'void'
+// CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .Store
+// CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'const MyStruct' lvalue
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const MyStruct' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'hlsl_constant MyStruct' lvalue Var {{.*}} 'one' 'hlsl_constant MyStruct'
+ one.Store();
+
+// CHECK: ExprWithCleanups {{.*}} 'void'
+// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'void'
+// CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .Store {{.*}}
+// CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'const MyStruct' lvalue
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const MyStruct' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'hlsl_constant MyStruct' lvalue Var {{.*}} 'two' 'hlsl_constant MyStruct'
+ two.Store();
+}
diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs-method-call.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs-method-call.hlsl
new file mode 100644
index 0000000000000..29019dd6f99c3
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-method-call.hlsl
@@ -0,0 +1,85 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -finclude-default-header -o - %s | llvm-cxxfilt | FileCheck %s
+
+struct MyStruct {
+ float f;
+ RWBuffer<float> Buf;
+
+ void Store() const {
+ Buf[0] = f;
+ }
+};
+
+// CHECK-DAG: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) }
+// CHECK-DAG: %__cblayout_CB = type <{ %__cblayout_MyStruct }>
+// CHECK-DAG: %__cblayout_MyStruct = type <{ float }>
+// CHECK-DAG: %struct.MyStruct = type { float, %"class.hlsl::RWBuffer" }
+cbuffer CB {
+ MyStruct one;
+}
+
+// CHECK-DAG: @one.Buf = internal global %"class.hlsl::RWBuffer" poison, align 4
+// CHECK-DAG: @CB.cb = internal global target("dx.CBuffer", %__cblayout_CB) poison
+// CHECK-DAG: @one = external hidden addrspace(2) global %__cblayout_MyStruct, align 4
+
+// $Globals constant buffer
+// CHECK-DAG: @"$Globals.cb" = internal global target("dx.CBuffer", %"__cblayout_$Globals") poison
+// CHECK-DAG: %"__cblayout_$Globals" = type <{ %__cblayout_MyStruct }>
+// CHECK-DAG: @two.Buf = internal global %"class.hlsl::RWBuffer" poison, align 4
+// CHECK-DAG: @two = external hidden addrspace(2) global %struct.MyStruct, align 4
+MyStruct two;
+
+// CHECK-DAG: @Constants = internal global %"class.hlsl::ConstantBuffer" poison, align 4
+// CHECK-DAG: %MyConstants = type <{ <4 x float>, <3 x i32> }>
+// CHECK-DAG: %struct.MyConstants = type { <4 x float>, <3 x i32> }
+
+struct MyConstants {
+ float4 vec;
+ int3 pos;
+ int3 getPosition() const { return pos; }
+};
+
+ConstantBuffer<MyConstants> Constants;
+
+// CHECK-LABEL: main
+[numthreads(4,1,1)]
+void main() {
+// CHECK: [[TMP1:%.*]] = alloca %struct.MyStruct, align 4
+// CHECK-NEXT: [[TMP2:%.*]] = alloca %struct.MyStruct, align 4
+// CHECK-NEXT: %pos = alloca <3 x i32>, align 4
+// CHECK-NEXT: [[TMP3:%.*]] = alloca %struct.MyConstants, align 1
+
+// Make sure we copy the struct from the constant buffer element by element to a temporary
+// variable and then call the method on that.
+
+// CHECK-NEXT: [[TMP1_F_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD0:%.*]] = load float, ptr addrspace(2) @one, align 4
+// CHECK-NEXT: store float [[CBUFLOAD0]], ptr [[TMP1_F_PTR]], align 4
+// CHECK-NEXT: [[TMP1_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: store ptr @one.Buf, ptr [[TMP1_BUF_PTR]], align 4
+// CHECK-NEXT: call void @MyStruct::Store() const(ptr {{.*}} [[TMP1]])
+ one.Store();
+
+// CHECK-NEXT: [[TMP2_F_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr [[TMP2]], i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD2:%.*]] = load float, ptr addrspace(2) @two, align 4
+// CHECK-NEXT: store float [[CBUFLOAD2]], ptr [[TMP2_F_PTR]], align 4
+// CHECK-NEXT: [[TMP2_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr [[TMP2]], i32 0, i32 1
+// CHECK-NEXT: store ptr @two.Buf, ptr [[TMP2_BUF_PTR]], align 4
+// CHECK-NEXT: call void @MyStruct::Store() const(ptr {{.*}} [[TMP2]])
+ two.Store();
+
+// CHECK-NEXT: [[CB_PTR:%.*]] = call {{.*}} ptr addrspace(2) @hlsl::ConstantBuffer<MyConstants>::operator MyConstants const AS2&() const(ptr {{.*}} @Constants)
+
+// CHECK-NEXT: [[CB_PTR_VEC:%.*]] = getelementptr inbounds %MyConstants, ptr addrspace(2) [[CB_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[TMP3_VEC_PTR:%.*]] = getelementptr inbounds %struct.MyConstants, ptr [[TMP3]], i32 0, i32 0
+// CHECK-NEXT: [[CBUFLOAD3:%.*]] = load <4 x float>, ptr addrspace(2) [[CB_PTR_VEC]], align 4
+// CHECK-NEXT: store <4 x float> [[CBUFLOAD3]], ptr [[TMP3_VEC_PTR]], align 4
+
+// CHECK-NEXT: [[CB_POS_PTR:%.*]] = getelementptr inbounds %MyConstants, ptr addrspace(2) [[CB_PTR]], i32 0, i32 1
+// CHECK-NEXT: [[TMP3_POS_PTR:%.*]] = getelementptr inbounds %struct.MyConstants, ptr [[TMP3]], i32 0, i32 1
+// CHECK-NEXT: [[CBUFLOAD4:%.*]] = load <3 x i32>, ptr addrspace(2) [[CB_POS_PTR]], align 4
+// CHECK-NEXT: store <3 x i32> [[CBUFLOAD4]], ptr [[TMP3_POS_PTR]], align 4
+
+// CHECK-NEXT: [[POS:%.*]] = call noundef <3 x i32> @MyConstants::getPosition() const(ptr {{.*}} [[TMP3]])
+// CHECK-NEXT: store <3 x i32> [[POS]], ptr %pos, align 4
+ int3 pos = Constants.getPosition();
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
index 179a4e1fec101..a5d8862294cf6 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
@@ -16,13 +16,9 @@ ConstantBuffer<S> CB;
[numthreads(4,1,1)]
void main() {
- // Bug: https://github.com/llvm/llvm-project/issues/153055
- // Calling non-const member function is allowed, but not implemented yet.
- // We should remove the expected error when done.
- // expected-error at +1 {{cannot initialize object parameter of type 'const S' with an expression of type 'const hlsl_constant S'}}
float tmp = CB.foo();
// Calling non-const member function is not allowed.
- // expected-error at +1 {{'this' argument to member function 'bar' has type 'const hlsl_constant S', but function is not marked const}}
+ // expected-error at +1 {{'this' argument to member function 'bar' has type 'const S', but function is not marked const}}
CB.bar();
}
More information about the cfe-commits
mailing list