[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