https://github.com/hekota updated 
https://github.com/llvm/llvm-project/pull/204232

>From 0566ff2861bf4db3c07128e80016cc1c0bde4558 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Mon, 15 Jun 2026 10:20:21 -0700
Subject: [PATCH 1/3] [HLSL] Codegen for passing cbuffer structs as function
 args

---
 clang/lib/CodeGen/CGCall.cpp                  |  18 ++-
 .../resources/cbuffer_struct_passing.hlsl     | 120 ++++++++++++++++++
 2 files changed, 131 insertions(+), 7 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 09f6d63a36bd6..82b374e50fd41 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5128,13 +5128,17 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, 
const Expr *E,
     return;
   }
 
-  if (HasAggregateEvalKind && isa<ImplicitCastExpr>(E) &&
-      cast<CastExpr>(E)->getCastKind() == CK_LValueToRValue &&
-      !type->isArrayParameterType() && !type.isNonTrivialToPrimitiveCopy()) {
-    LValue L = EmitLValue(cast<CastExpr>(E)->getSubExpr());
-    assert(L.isSimple());
-    args.addUncopiedAggregate(L, type);
-    return;
+  if (HasAggregateEvalKind) {
+    auto *ICE = dyn_cast<ImplicitCastExpr>(E);
+    if (ICE && ICE->getCastKind() == CK_LValueToRValue &&
+        ICE->getSubExpr()->getType().getAddressSpace() !=
+            LangAS::hlsl_constant &&
+        !type->isArrayParameterType() && !type.isNonTrivialToPrimitiveCopy()) {
+      LValue L = EmitLValue(cast<CastExpr>(E)->getSubExpr());
+      assert(L.isSimple());
+      args.addUncopiedAggregate(L, type);
+      return;
+    }
   }
 
   args.add(EmitAnyExprToTemp(E), type);
diff --git a/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing.hlsl 
b/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing.hlsl
index d29ec8e9f87b9..f33baad90c883 100644
--- a/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing.hlsl
+++ b/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing.hlsl
@@ -237,3 +237,123 @@ void case4() {
 
 // CHECK-NEXT: ret void
 }
+
+void useS(S s) {}
+
+void case5() {
+// CHECK-LABEL: case5
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[AggTemp1:%.*]] = alloca %struct.S, align 1
+// CHECK-NEXT: [[AggTemp2:%.*]] = alloca %struct.S, align 1
+
+//
+// Copy S field by field into temporary variable in default address space.
+//
+// CHECK-NEXT: [[Ptr_a:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp1]], i32 0, i32 0
+// CHECK-NEXT: [[CBufLoad_a:%.*]] = load <3 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, align 4
+// CHECK-NEXT: store <3 x float> [[CBufLoad_a]], ptr [[Ptr_a]], align 4
+
+// CHECK-NEXT: [[Ptr_b:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp1]], i32 0, i32 1
+// CHECK-NEXT: [[CBufLoad_b:%.*]] = load double, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, {{i32|i64}} 16), align 8
+// CHECK-NEXT: store double [[CBufLoad_b]], ptr [[Ptr_b]], align 8
+
+// CHECK-NEXT: [[Ptr_c:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp1]], i32 0, i32 2
+// CHECK-NEXT: [[CBufLoad_c:%.*]] = load <4 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, {{i32|i64}} 32), align 4
+// CHECK-NEXT: store <4 x float> [[CBufLoad_c]], ptr [[Ptr_c]], align 4
+
+// Call useS with the temporary variable.
+// CHECK-NEXT: call {{.*}}void @_Z4useS1S(ptr noundef dead_on_return 
[[AggTemp1]])
+  useS(cbs);
+
+//
+// Copy T.s field by field into temporary variable in default address space.
+//
+// CHECK-NEXT: [[Ptr_a:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp2]], i32 0, i32 0
+// CHECK-NEXT: [[CBufLoad_a:%.*]] = load <3 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, align 4
+// CHECK-NEXT: store <3 x float> [[CBufLoad_a]], ptr [[Ptr_a]], align 4
+
+// CHECK-NEXT: [[Ptr_b:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp2]], i32 0, i32 1
+// CHECK-NEXT: [[CBufLoad_b:%.*]] = load double, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 16), align 8
+// CHECK-NEXT: store double [[CBufLoad_b]], ptr [[Ptr_b]], align 8
+
+// CHECK-NEXT: [[Ptr_c:%.*]] = getelementptr inbounds %struct.S, ptr 
[[AggTemp2]], i32 0, i32 2
+// CHECK-NEXT: [[CBufLoad_c:%.*]] = load <4 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 32), align 4
+// CHECK-NEXT: store <4 x float> [[CBufLoad_c]], ptr [[Ptr_c]], align 4
+
+// Call useS with the temporary variable.
+// CHECK-NEXT: call {{.*}}void @_Z4useS1S(ptr noundef dead_on_return 
[[AggTemp2]])
+  useS(cbt.s);
+
+// CHECK-NEXT: ret void
+}
+
+void useT(T t) {
+}
+
+void case6() {
+// CHECK-LABEL: case6
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call token @llvm.experimental.convergence.entry()
+// CHECK-NEXT: [[AggTemp:%.*]] = alloca %struct.T, align 1
+
+// Check that constant to default address space copies the struct field by 
field
+// into a temporary variable before passing it to the function.
+//
+// CHECK-NEXT: [[Ptr_s:%.*]] = getelementptr inbounds %struct.T, ptr 
[[AggTemp]], i32 0, i32 0
+// CHECK-NEXT: [[Ptr_a:%.*]] = getelementptr inbounds %struct.S, ptr 
[[Ptr_s]], i32 0, i32 0
+// CHECK-NEXT: [[CbufLoad_a:%.*]] = load <3 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, align 4
+// CHECK-NEXT: store <3 x float> [[CbufLoad_a]], ptr [[Ptr_a]], align 4
+
+// CHECK-NEXT: [[Ptr_b:%.*]] = getelementptr inbounds %struct.S, ptr 
[[Ptr_s]], i32 0, i32 1
+// CHECK-NEXT: [[CbufLoad_c:%.*]] = load double, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 16), align 8
+// CHECK-NEXT: store double [[CbufLoad_c]], ptr [[Ptr_b]], align 8
+
+// CHECK-NEXT: [[Ptr_c:%.*]] = getelementptr inbounds %struct.S, ptr 
[[Ptr_s]], i32 0, i32 2
+// CHECK-NEXT: [[CbufLoad_b:%.*]] = load <4 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 32), align 4
+// CHECK-NEXT: store <4 x float> [[CbufLoad_b]], ptr [[Ptr_c]], align 4
+  
+// CHECK-NEXT: [[Ptr_arr:%.*]] = getelementptr inbounds %struct.T, ptr 
[[AggTemp]], i32 0, i32 1
+// CHECK-NEXT: [[Ptr_arr0:%.*]] = getelementptr inbounds [2 x i32], ptr 
[[Ptr_arr]], i32 0, i32 0
+// CHECK-NEXT: [[CbufLoad_arr0:%.*]] = load i32, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 48), align 4
+// CHECK-NEXT: store i32 [[CbufLoad_arr0]], ptr [[Ptr_arr0]], align 4
+
+// CHECK-NEXT: [[Ptr_arr1:%.*]] = getelementptr inbounds [2 x i32], ptr 
[[Ptr_arr]], i32 0, i32 1
+// CHECK-NEXT: [[CbufLoad_arr1:%.*]] = load i32, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbt, {{i32|i64}} 64), align 4
+// CHECK-NEXT: store i32 [[CbufLoad_arr1]], ptr [[Ptr_arr1]], align 4
+
+// Call useT with the temporary variable
+// CHECK-NEXT: call {{.*}}void @_Z4useT1T(ptr noundef dead_on_return 
[[AggTemp]])
+  useT(cbt);
+}
+
+void useP(P p) {}
+
+void case7() {
+// CHECK-LABEL: case7
+// CHECK-NEXT: entry:
+// CHECK-NEXT: call token @llvm.experimental.convergence.entry()
+
+// CHECK-NEXT: [[TempP:%.*]] = alloca %struct.P, align 1
+// CHECK-NEXT: [[TempS:%.*]] = alloca %struct.S, align 1
+
+// Check that constant to default address space copies the S struct field by 
field
+// into a temporary variable and converts it to the base class P before 
passing it to the function.
+
+// CHECK-NEXT: [[Tmp0Ptr_a1:%.*]] = getelementptr inbounds %struct.S, ptr 
[[TempS]], i32 0, i32 0
+// CHECK-NEXT: [[CbufLoad_a1:%.*]] = load <3 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, align 4
+// CHECK-NEXT: store <3 x float> [[CbufLoad_a1]], ptr [[Tmp0Ptr_a1]], align 4
+
+// CHECK-NEXT: [[Tmp0Ptr_b1:%.*]] = getelementptr inbounds %struct.S, ptr 
[[TempS]], i32 0, i32 1
+// CHECK-NEXT: [[CbufLoad_b1:%.*]] = load double, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, {{i32|i64}} 16), align 8
+// CHECK-NEXT: store double [[CbufLoad_b1]], ptr [[Tmp0Ptr_b1]], align 8
+
+// CHECK-NEXT: [[Tmp0Ptr_c1:%.*]] = getelementptr inbounds %struct.S, ptr 
[[TempS]], i32 0, i32 2
+// CHECK-NEXT: [[CbufLoad_c1:%.*]] = load <4 x float>, ptr 
addrspace([[CONST_ADDR_SPACE]]) getelementptr inbounds nuw (i8, ptr 
addrspace([[CONST_ADDR_SPACE]]) @cbs, {{i32|i64}} 32), align 4
+// CHECK-NEXT: store <4 x float> [[CbufLoad_c1]], ptr [[Tmp0Ptr_c1]], align 4
+
+// Convert to P temporary and call useP
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.{{i32|i64}}(ptr align 1 [[TempP]], 
ptr align 1 [[TempS]], {{i32|i64}} 12, i1 false)
+// CHECK-NEXT: call {{.*}}void @_Z4useP1P(ptr noundef dead_on_return [[TempP]])
+  useP(cbs);
+}

>From edc5175cef29023304d8483cd00caf6a25faf302 Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Tue, 16 Jun 2026 12:44:37 -0700
Subject: [PATCH 2/3] [HLSL] Implement codegen for copying cbuffer structs with
 resources

Global-scope structs are in `hlsl_constant` address space and use cbuffer 
layout.
When those structs contain resources, the resources are not stored inline in the
constant buffer. Instead, they are represented as separate globals, or in case 
of
resource arrays are initialized on demand.

This change implements the HLSL codegen for cases where a cbuffer-backed struct 
with
embedded resources is copied into a local variable or passed as a function 
argument.
CodeGen materializes a temporary in the default address space, copies the 
constant-data
fields using the cbuffer struct layout, and reconstruct the resource members in 
the
local copy.

Depends on #203961.

Fixes #182990
---
 clang/include/clang/AST/HLSLResource.h        |   2 +
 clang/lib/CodeGen/CGExprAgg.cpp               |  14 +-
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 203 +++++++--
 clang/lib/CodeGen/CGHLSLRuntime.h             |   4 +-
 clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp |   6 +
 clang/lib/CodeGen/HLSLBufferLayoutBuilder.h   |   1 +
 ...cbuffer_struct_passing_with_resources.hlsl | 385 ++++++++++++++++++
 7 files changed, 581 insertions(+), 34 deletions(-)
 create mode 100644 
clang/test/CodeGenHLSL/resources/cbuffer_struct_passing_with_resources.hlsl

diff --git a/clang/include/clang/AST/HLSLResource.h 
b/clang/include/clang/AST/HLSLResource.h
index 585435ec1dd33..1acfecb0353ed 100644
--- a/clang/include/clang/AST/HLSLResource.h
+++ b/clang/include/clang/AST/HLSLResource.h
@@ -154,6 +154,8 @@ class EmbeddedResourceNameBuilder {
     return &AST.Idents.get(Name);
   }
 
+  llvm::StringRef getName() const { return Name; }
+
 private:
   void pushName(llvm::StringRef N, llvm::StringRef Delim);
 };
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 8540603c28e9a..bc35ffdaad2bd 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -254,6 +254,10 @@ void AggExprEmitter::EmitAggLoadOfLValue(const Expr *E) {
     return;
   }
 
+  if (E->getType().getAddressSpace() == LangAS::hlsl_constant)
+    if (CGF.CGM.getHLSLRuntime().emitBufferCopy(CGF, E, LV, Dest))
+      return;
+
   EmitFinalDestCopy(E->getType(), LV);
 }
 
@@ -2318,7 +2322,9 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, 
LValue Src, QualType Ty,
               Record->hasTrivialCopyAssignment() ||
               Record->hasTrivialMoveConstructor() ||
               Record->hasTrivialMoveAssignment() ||
-              Record->hasAttr<TrivialABIAttr>() || Record->isUnion()) &&
+              Record->hasAttr<TrivialABIAttr>() || Record->isUnion() ||
+              // HLSL uses aggregate-copy for user-defined record types.
+              (getLangOpts().HLSL && !Record->isHLSLBuiltinRecord())) &&
              "Trying to aggregate-copy a type without a trivial copy/move "
              "constructor or assignment operator");
       // Ignore empty classes in C++.
@@ -2339,9 +2345,9 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, 
LValue Src, QualType Ty,
     }
   }
 
-  if (getLangOpts().HLSL && Ty.getAddressSpace() == LangAS::hlsl_constant)
-    if (CGM.getHLSLRuntime().emitBufferCopy(*this, DestPtr, SrcPtr, Ty))
-      return;
+  assert(Ty.getAddressSpace() != LangAS::hlsl_constant &&
+         "copies of aggregates in hlsl_constant address space should be "
+         "handled earlier by the HLSL runtime");
 
   // Aggregate assignment turns into llvm.memcpy.  This is almost valid per
   // C99 6.5.16.1p3, which states "If the value being stored in an object is
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 40b29559c2a1c..1c6407b75ed0f 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -101,6 +101,13 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion 
RootSigVer,
   RootSignatureValMD->addOperand(MDVals);
 }
 
+static void copyGlobalResource(CodeGenFunction &CGF, const VarDecl *ResourceVD,
+                               AggValueSlot &DestSlot) {
+  GlobalVariable *ResGV =
+      cast<GlobalVariable>(CGF.CGM.GetAddrOfGlobalVar(ResourceVD));
+  CGF.Builder.CreateStore(ResGV, DestSlot.getAddress());
+}
+
 // Given a MemberExpr of a resource or resource array type, find the parent
 // VarDecl of the struct or class instance that contains this resource and
 // build the full resource name based on the member access path.
@@ -109,12 +116,16 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion 
RootSigVer,
 // this function will find the VarDecl of "myStructArray" and use the
 // EmbeddedResourceNameBuilder to build the resource name
 // "myStructArray.0.memberA".
+//
+// This also works for a record type expression that has some embedded
+// resources. It finds the parent VarDecl of that record and builds a partial
+// name which is the prefix of the resource globals associated with the
+// declaration.
 static const VarDecl *findStructResourceParentDeclAndBuildName(
-    const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) {
+    const Expr *E, EmbeddedResourceNameBuilder &NameBuilder) {
 
   SmallVector<const Expr *> WorkList;
   const VarDecl *VD = nullptr;
-  const Expr *E = ME;
 
   for (;;) {
     if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
@@ -481,6 +492,8 @@ class HLSLBufferCopyEmitter {
   SmallVector<llvm::Value *> CurStoreIndices;
   SmallVector<llvm::Value *> CurLoadIndices;
 
+  using EmitResourceFn = llvm::function_ref<void(AggValueSlot &)>;
+
   // Creates & returns either a structured.gep or a ptradd/gep depending on
   // langopts.
   llvm::Value *emitAccessChain(llvm::Type *BaseTy, llvm::Value *Base,
@@ -523,8 +536,39 @@ class HLSLBufferCopyEmitter {
     return true;
   }
 
+  // Returns true if the type is either a struct represening a resource record,
+  // or an array of structs that are resource records. This assumes a struct is
+  // a resource record if the first element is a target type (resource handle).
+  // This is the case for all target types used by HLSL except the padding type
+  // ("{dx|spirv.Padding"), but padding will never be the first element of a
+  // struct.
+  bool isResourceOrResourceArray(llvm::Type *Ty) {
+    while (auto *AT = dyn_cast<llvm::ArrayType>(Ty))
+      Ty = AT->getElementType();
+
+    auto *ST = dyn_cast<llvm::StructType>(Ty);
+    if (!ST || ST->getNumElements() < 1)
+      return false;
+
+    auto *TargetTy = dyn_cast<llvm::TargetExtType>(ST->getElementType(0));
+    return TargetTy != nullptr;
+  }
+
+  void emitResourceOrResourceArray(Value *Dst, llvm::Type *DstTy,
+                                   EmitResourceFn EmitResFn) {
+    CharUnits DstAlign =
+        
CharUnits::fromQuantity(CGF.CGM.getDataLayout().getABITypeAlign(DstTy));
+    Address DstAddr(Dst, DstTy, DstAlign);
+    AggValueSlot Slot = AggValueSlot::forAddr(
+        DstAddr, Qualifiers(), AggValueSlot::IsDestructed_t(true),
+        AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
+        AggValueSlot::DoesNotOverlap);
+
+    EmitResFn(Slot);
+  }
+
   void emitBufferLayoutCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
-                            llvm::ArrayType *DstTy) {
+                            llvm::ArrayType *DstTy, EmitResourceFn EmitResFn) {
     // Those assumptions are checked by isBufferLayoutArray.
     auto *SrcPaddedArrayTy = cast<llvm::ArrayType>(SrcTy->getElementType(0));
     assert(SrcPaddedArrayTy->getNumElements() + 1 == DstTy->getNumElements());
@@ -538,7 +582,8 @@ class HLSLBufferCopyEmitter {
       auto Index = llvm::ConstantInt::get(CGF.IntTy, I);
       auto *SrcElt = emitAccessChain(SrcTy, Src, {Zero, Index, Zero});
       auto *DstElt = emitAccessChain(DstTy, Dst, {Index});
-      emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType());
+      emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType(),
+                      EmitResFn);
     }
 
     auto *SrcElt =
@@ -546,62 +591,78 @@ class HLSLBufferCopyEmitter {
     auto *DstElt = emitAccessChain(
         DstTy, Dst,
         {llvm::ConstantInt::get(CGF.IntTy, DstTy->getNumElements() - 1)});
-    emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType());
+    emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType(),
+                    EmitResFn);
   }
 
   void emitCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
-                llvm::Type *DstTy) {
+                llvm::Type *DstTy, EmitResourceFn EmitResFn) {
+    assert(!isResourceOrResourceArray(DstTy) &&
+           "direct access to resources or resource arrays should be handled "
+           "separately");
+
     if (isBufferLayoutArray(SrcTy))
-      return emitBufferLayoutCopy(Src, SrcTy, Dst,
-                                  cast<llvm::ArrayType>(DstTy));
+      return emitBufferLayoutCopy(Src, SrcTy, Dst, 
cast<llvm::ArrayType>(DstTy),
+                                  EmitResFn);
 
     unsigned SrcIndex = 0;
     unsigned DstIndex = 0;
 
+    // DstTy layout is in default address space and can include resource types.
+    // SrcTy is in cbuffer layout where resources are filtered out, so the
+    // number of elements in SrcTy can be less than the number of elements in
+    // DstTy.
     auto *DstST = cast<llvm::StructType>(DstTy);
-    while (SrcIndex < SrcTy->getNumElements() &&
-           DstIndex < DstST->getNumElements()) {
-      if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(
-              SrcTy->getElementType(SrcIndex))) {
-        SrcIndex += 1;
+    while (DstIndex < DstST->getNumElements()) {
+      llvm::Type *DstEltTy = DstST->getElementType(DstIndex);
+      if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(DstEltTy)) {
+        DstIndex += 1;
         continue;
       }
-
-      if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(
-              DstST->getElementType(DstIndex))) {
+      if (isResourceOrResourceArray(DstEltTy)) {
+        auto *DstElt = emitAccessChain(
+            DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, DstIndex)});
+        emitResourceOrResourceArray(DstElt, DstEltTy, EmitResFn);
         DstIndex += 1;
         continue;
       }
 
+      assert(SrcIndex < SrcTy->getNumElements());
+      llvm::Type *SrcEltTy = SrcTy->getElementType(SrcIndex);
+      if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(SrcEltTy)) {
+        SrcIndex += 1;
+        continue;
+      }
+
       auto *SrcElt = emitAccessChain(
           SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, SrcIndex)});
       auto *DstElt = emitAccessChain(
           DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, DstIndex)});
-      emitElementCopy(SrcElt, SrcTy->getElementType(SrcIndex), DstElt,
-                      DstST->getElementType(DstIndex));
+      emitElementCopy(SrcElt, SrcEltTy, DstElt, DstEltTy, EmitResFn);
       DstIndex += 1;
       SrcIndex += 1;
     }
   }
 
   void emitCopy(Value *Src, llvm::ArrayType *SrcTy, Value *Dst,
-                llvm::Type *DstTy) {
+                llvm::Type *DstTy, EmitResourceFn EmitResFn) {
     for (unsigned I = 0, E = SrcTy->getNumElements(); I < E; ++I) {
       auto *SrcElt =
           emitAccessChain(SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, I)});
       auto *DstElt =
           emitAccessChain(DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, I)});
       emitElementCopy(SrcElt, SrcTy->getElementType(), DstElt,
-                      cast<llvm::ArrayType>(DstTy)->getElementType());
+                      cast<llvm::ArrayType>(DstTy)->getElementType(),
+                      EmitResFn);
     }
   }
 
   void emitElementCopy(Value *Src, llvm::Type *SrcTy, Value *Dst,
-                       llvm::Type *DstTy) {
+                       llvm::Type *DstTy, EmitResourceFn EmitResFn) {
     if (auto *AT = dyn_cast<llvm::ArrayType>(SrcTy))
-      return emitCopy(Src, AT, Dst, DstTy);
+      return emitCopy(Src, AT, Dst, DstTy, EmitResFn);
     if (auto *ST = dyn_cast<llvm::StructType>(SrcTy))
-      return emitCopy(Src, ST, Dst, DstTy);
+      return emitCopy(Src, ST, Dst, DstTy, EmitResFn);
 
     // When we have a scalar or vector element we can emit the copy.
     CharUnits SrcAlign =
@@ -618,7 +679,7 @@ class HLSLBufferCopyEmitter {
   HLSLBufferCopyEmitter(CodeGenFunction &CGF, Address DstPtr, Address SrcPtr)
       : CGF(CGF), DstPtr(DstPtr), SrcPtr(SrcPtr) {}
 
-  bool emitCopy(QualType CType) {
+  bool emitCopy(QualType CType, EmitResourceFn EmitResFn = nullptr) {
     LayoutTy = HLSLBufferLayoutBuilder(CGF.CGM).layOutType(CType);
 
     // TODO: We should be able to fall back to a regular memcpy if the layout
@@ -627,11 +688,53 @@ class HLSLBufferCopyEmitter {
     //
     // See https://github.com/llvm/wg-hlsl/issues/351
     emitElementCopy(SrcPtr.getBasePointer(), LayoutTy, DstPtr.getBasePointer(),
-                    DstPtr.getElementType());
+                    DstPtr.getElementType(), EmitResFn);
     return true;
   }
 };
 
+// Represents a list resources associated with a global struct whose name
+// starts with the specified prefix.
+// The order of HLSLAssociatedResourceDeclAttr attributes is identical to the
+// order of the depth-first traversal of the corresponding fields in the 
struct.
+// The resources are always returned in that order, which is the same order
+// we need when a struct is copied element-by-element.
+class AssociatedResourcesList {
+  specific_attr_iterator<HLSLAssociatedResourceDeclAttr> Begin, End, Next;
+
+public:
+  AssociatedResourcesList(const VarDecl *StructVD,
+                          StringRef ResourceNamePrefix) {
+    auto I = StructVD->specific_attr_begin<HLSLAssociatedResourceDeclAttr>();
+    auto E = StructVD->specific_attr_end<HLSLAssociatedResourceDeclAttr>();
+
+    // Skip over associated resources that don't match the prefix.
+    while (I != E &&
+           !I->getResDecl()->getName().starts_with(ResourceNamePrefix))
+      ++I;
+    assert(I != E && "expected associated resource not found");
+    Begin = End = I;
+
+    // Scan over associated resources that do match the prefix to find the end
+    // of the range.
+    while (I != E && ((HLSLAssociatedResourceDeclAttr *)*I)
+                         ->getResDecl()
+                         ->getName()
+                         .starts_with(ResourceNamePrefix))
+      End = ++I;
+    Next = Begin;
+  }
+
+  const VarDecl *getNextResource() {
+    if (Next == End)
+      return nullptr;
+
+    const VarDecl *Res = Next->getResDecl();
+    ++Next;
+    return Res;
+  }
+};
+
 } // namespace
 
 llvm::Type *
@@ -1917,9 +2020,53 @@ CGHLSLRuntime::emitResourceMemberExpr(CodeGenFunction 
&CGF,
   return LV;
 }
 
-bool CGHLSLRuntime::emitBufferCopy(CodeGenFunction &CGF, Address DstPtr,
-                                   Address SrcPtr, QualType CType) {
-  return HLSLBufferCopyEmitter(CGF, DstPtr, SrcPtr).emitCopy(CType);
+bool CGHLSLRuntime::emitBufferCopy(CodeGenFunction &CGF, const Expr *E,
+                                   const LValue &SrcLV,
+                                   AggValueSlot &DestSlot) {
+  assert(E->getType().getAddressSpace() == LangAS::hlsl_constant &&
+         "expected expression in HLSL constant address space");
+  assert(!E->getType()->isHLSLResourceRecord() &&
+         !E->getType()->isHLSLResourceRecordArray() &&
+         "direct accesses to resource types should be handled separately");
+
+  if (DestSlot.isIgnored())
+    return false;
+
+  QualType Ty = E->getType();
+  Address DstPtr = DestSlot.getAddress();
+  Address SrcPtr = SrcLV.getAddress();
+
+  // If there are no intangible types, we don't need to lookup associated
+  // resources.
+  if (!Ty->isHLSLIntangibleType())
+    return HLSLBufferCopyEmitter(CGF, DstPtr, SrcPtr).emitCopy(Ty);
+
+  // Handle structs with intangible types by setting the resource fields
+  // of the destination struct with the resources associated with the global
+  // struct.
+  EmbeddedResourceNameBuilder NameBuilder;
+  const VarDecl *VD = findStructResourceParentDeclAndBuildName(E, NameBuilder);
+  AssociatedResourcesList AssociatedResources(VD, NameBuilder.getName());
+
+  // Callback to fill in the associated resource.
+  auto EmitResFn = [&](AggValueSlot &ResSlot) {
+    const VarDecl *ResDecl = AssociatedResources.getNextResource();
+    assert(ResDecl && "associated resource declaration not found");
+
+    // Check that the resource type of dest and src matches.
+    [[maybe_unused]] llvm::Type *DestType =
+        ResSlot.getAddress().getElementType();
+    [[maybe_unused]] llvm::Type *SrcConvertedType =
+        CGM.getTypes().ConvertTypeForMem(ResDecl->getType());
+    assert(DestType == SrcConvertedType && "resource slot type mismatch");
+
+    if (ResDecl->getType()->isHLSLResourceRecord())
+      copyGlobalResource(CGF, ResDecl, ResSlot);
+    else
+      initializeGlobalResourceArray(CGF, ResDecl, ResSlot);
+  };
+
+  return HLSLBufferCopyEmitter(CGF, DstPtr, SrcPtr).emitCopy(Ty, EmitResFn);
 }
 
 LValue CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF,
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h 
b/clang/lib/CodeGen/CGHLSLRuntime.h
index cafa073fc4e68..b936030df33d1 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -315,8 +315,8 @@ class CGHLSLRuntime {
   RawAddress createBufferMatrixTempAddress(const LValue &LV,
                                            CodeGenFunction &CGF);
 
-  bool emitBufferCopy(CodeGenFunction &CGF, Address DestPtr, Address SrcPtr,
-                      QualType CType);
+  bool emitBufferCopy(CodeGenFunction &CGF, const Expr *E, const LValue &SrcLV,
+                      AggValueSlot &DestSlot);
 
   LValue emitBufferMemberExpr(CodeGenFunction &CGF, const MemberExpr *E);
   std::optional<LValue> emitResourceMemberExpr(CodeGenFunction &CGF,
diff --git a/clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp 
b/clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp
index 9a18e84b7339d..b09fc4c91df2a 100644
--- a/clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp
+++ b/clang/lib/CodeGen/HLSLBufferLayoutBuilder.cpp
@@ -61,6 +61,8 @@ HLSLBufferLayoutBuilder::layOutStruct(const RecordType *RT,
   CharUnits CurrentOffset = CharUnits::Zero();
   for (auto &[FD, Offset] : FieldsWithOffset) {
     llvm::Type *LayoutType = layOutType(FD->getType());
+    if (!LayoutType)
+      continue;
 
     const llvm::DataLayout &DL = CGM.getDataLayout();
     CharUnits Size =
@@ -142,6 +144,10 @@ llvm::Type *HLSLBufferLayoutBuilder::layOutMatrix(QualType 
Ty) {
 }
 
 llvm::Type *HLSLBufferLayoutBuilder::layOutType(QualType Ty) {
+  // HLSL resource types are not included in the buffer layout.
+  if (Ty->isHLSLResourceRecord() || Ty->isHLSLResourceRecordArray())
+    return nullptr;
+
   if (const auto *AT = CGM.getContext().getAsConstantArrayType(Ty))
     return layOutArray(AT);
 
diff --git a/clang/lib/CodeGen/HLSLBufferLayoutBuilder.h 
b/clang/lib/CodeGen/HLSLBufferLayoutBuilder.h
index 8f1530308bf30..4e7fb3e11768f 100644
--- a/clang/lib/CodeGen/HLSLBufferLayoutBuilder.h
+++ b/clang/lib/CodeGen/HLSLBufferLayoutBuilder.h
@@ -38,6 +38,7 @@ class HLSLBufferLayoutBuilder {
   ///
   /// The function iterates over all fields of the record type (including base
   /// classes) and works out a padded llvm type to represent the buffer layout.
+  /// If the field is a resource or resource array, it will be ignored.
   ///
   /// If a non-empty OffsetInfo is provided (ie, from `packoffset` annotations
   /// in the source), any provided offsets offsets will be respected. If the
diff --git 
a/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing_with_resources.hlsl 
b/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing_with_resources.hlsl
new file mode 100644
index 0000000000000..8c2f03e23b879
--- /dev/null
+++ 
b/clang/test/CodeGenHLSL/resources/cbuffer_struct_passing_with_resources.hlsl
@@ -0,0 +1,385 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library -emit-llvm 
-disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
+
+struct MyStruct {
+  int a;
+  RWBuffer<float> Buf;
+};
+
+struct MyStructWithCounter {
+  RWStructuredBuffer<int> StructBuf;
+  float f;
+};
+
+struct WrappaStruct {
+  float b;
+  MyStruct s[2];
+  RWStructuredBuffer<int> BufArray[2];
+};
+
+cbuffer CB {
+  MyStruct cbs;
+  MyStructWithCounter cbsWithCounter;
+  WrappaStruct cbw;
+}
+
+// Resource record types
+// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 
0, 0) }
+// CHECK: %"class.hlsl::RWStructuredBuffer" = type { target("dx.RawBuffer", 
i32, 1, 0), target("dx.RawBuffer", i32, 1, 0) }
+
+// cbuffer layout structs
+// CHECK: %__cblayout_CB = type <{ %__cblayout_MyStruct, target("dx.Padding", 
12), %__cblayout_MyStructWithCounter, target("dx.Padding", 12), 
%__cblayout_WrappaStruct }>
+// CHECK: %__cblayout_MyStruct = type <{ i32 }>
+// CHECK: %__cblayout_MyStructWithCounter = type <{ float }>
+// CHECK: %__cblayout_WrappaStruct = type <{ float, target("dx.Padding", 12), 
<{ [1 x <{ %__cblayout_MyStruct, target("dx.Padding", 12) }>], 
%__cblayout_MyStruct }> }>
+
+// struct in default address space
+// CHECK: %struct.MyStruct = type { i32, %"class.hlsl::RWBuffer" }
+// CHECK: %struct.MyStructWithCounter = type { 
%"class.hlsl::RWStructuredBuffer", float }
+// CHECK: %struct.WrappaStruct = type { float, [2 x %struct.MyStruct], [2 x 
%"class.hlsl::RWStructuredBuffer"] }
+
+// Resource globals associated with the cbuffer structs.
+// Only individual resources have globals. Resource arrays such as 
WrappaStruct::BufArray
+// are initialized on access.
+// CHECK: @cbs.Buf = internal global %"class.hlsl::RWBuffer" poison, align 4
+// CHECK: @cbsWithCounter.StructBuf = internal global 
%"class.hlsl::RWStructuredBuffer" poison, align 4
+// CHECK: @cbw.s.0.Buf = internal global %"class.hlsl::RWBuffer" poison, align 
4
+// CHECK: @cbw.s.1.Buf = internal global %"class.hlsl::RWBuffer" poison, align 
4
+
+void useMyStruct(MyStruct s) {}
+
+void useMyStructWithCounter(MyStructWithCounter s) {}
+
+void useWrappaStruct(WrappaStruct w) {}
+
+// Simple struct with one resource - local initialization
+// CHECK-LABEL: case1
+void case1() {
+// CHECK: %s1 = alloca %struct.MyStruct, align 4
+// CHECK: %sc1 = alloca %struct.MyStructWithCounter, align 4
+
+// s1.a - copy from cbuffer
+// CHECK-NEXT: [[S1_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr 
%s1, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_A:%.*]] = load i32, ptr addrspace(2) @cbs, align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_A]], ptr [[S1_A_PTR]], align 4
+
+// s1.Buf - use global resource @cbs.Buf
+// CHECK-NEXT: [[S1_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr %s1, i32 0, i32 1
+// CHECK-NEXT: store ptr @cbs.Buf, ptr [[S1_BUF_PTR]], align 4
+
+  MyStruct s1 = cbs;
+
+// sc1.StructBuf - use global resource @cbsWithCounter.StructBuf
+// CHECK-NEXT: [[SC1_STRUCT_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr %sc1, i32 0, i32 0
+// CHECK-NEXT: store ptr @cbsWithCounter.StructBuf, ptr 
[[SC1_STRUCT_BUF_PTR]], align 4
+
+// sc1.f - copy from cbuffer
+// CHECK-NEXT: [[SC1_F_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr %sc1, i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD_F:%.*]] = load float, ptr addrspace(2) 
@cbsWithCounter, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_F]], ptr [[SC1_F_PTR]], align 4
+  
+  MyStructWithCounter sc1 = cbsWithCounter;
+}
+
+// Simple struct with one resource - assignment
+// CHECK-LABEL: case2
+void case2() {
+// CHECK: %s2 = alloca %struct.MyStruct, align 4
+// CHECK-NEXT: [[TMP1:%.*]] = alloca %struct.MyStruct, align 4
+// CHECK: %sc2 = alloca %struct.MyStructWithCounter, align 4
+// CHECK-NEXT: [[TMP2:%.*]] = alloca %struct.MyStructWithCounter, align 4
+
+// s2.a - copy from cbuffer
+// CHECK-NEXT: [[S2_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr 
%s2, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_A:%.*]] = load i32, ptr addrspace(2) @cbs, align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_A]], ptr [[S2_A_PTR]], align 4
+
+// s2.Buf - use global resource @cbs.Buf
+// CHECK-NEXT: [[S2_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr %s2, i32 0, i32 1
+// CHECK-NEXT: store ptr @cbs.Buf, ptr [[S2_BUF_PTR]], align 4
+
+// result of the assignment expression passed along in a temporary
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[TMP1]], ptr 
align 4 %s2, i32 8, i1 false)
+
+  MyStruct s2;
+  s2 = cbs;
+
+// sc2.StructBuf - use global resource @cbsWithCounter.StructBuf
+// CHECK-NEXT: [[SC2_STRUCT_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr %sc2, i32 0, i32 0
+// CHECK-NEXT: store ptr @cbsWithCounter.StructBuf, ptr 
[[SC2_STRUCT_BUF_PTR]], align 4
+
+// sc2.f - copy from cbuffer
+// CHECK-NEXT: [[SC2_F_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr %sc2, i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD_F:%.*]] = load float, ptr addrspace(2) 
@cbsWithCounter, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_F]], ptr [[SC2_F_PTR]], align 4
+
+// result of the assignment expression passed along in a temporary
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[TMP2]], ptr 
align 4 %sc2, i32 12, i1 false)
+
+  MyStructWithCounter sc2;
+  sc2 = cbsWithCounter;
+}
+
+// Simple struct with one resource - function argument from cbuffer
+// CHECK-LABEL: case3
+void case3() {
+// CHECK: [[TMP1:%.*]] = alloca %struct.MyStruct, align 4
+// CHECK: [[TMP2:%.*]] = alloca %struct.MyStructWithCounter, align 4
+
+// tmp1.a - copy from cbuffer
+// CHECK-NEXT: [[TMP1_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[TMP1]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_A:%.*]] = load i32, ptr addrspace(2) @cbs, align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_A]], ptr [[TMP1_A_PTR]], align 4
+
+// tmp1.Buf - use global resource @cbs.Buf
+// CHECK-NEXT: [[TMP1_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[TMP1]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbs.Buf, ptr [[TMP1_BUF_PTR]], align 4
+
+// call useMyStruct with the temporary
+// CHECK-NEXT: call void @useMyStruct(MyStruct)(ptr noundef dead_on_return 
[[TMP1]])
+
+  useMyStruct(cbs); 
+
+// tmp2.StructBuf - use global resource @cbsWithCounter.StructBuf
+// CHECK-NEXT: [[TMP2_STRUCT_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr [[TMP2]], i32 0, i32 0
+// CHECK-NEXT: store ptr @cbsWithCounter.StructBuf, ptr 
[[TMP2_STRUCT_BUF_PTR]], align 4
+
+// tmp2.f - copy from cbuffer
+// CHECK-NEXT: [[TMP2_F_PTR:%.*]] = getelementptr inbounds 
%struct.MyStructWithCounter, ptr [[TMP2]], i32 0, i32 1
+// CHECK-NEXT: [[CBUF_LOAD_F:%.*]] = load float, ptr addrspace(2) 
@cbsWithCounter, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_F]], ptr [[TMP2_F_PTR]], align 4
+
+// call useMyStructWithCounter with the temporary
+// CHECK-NEXT: call void @useMyStructWithCounter(MyStructWithCounter)(ptr 
noundef dead_on_return [[TMP2]])
+
+  useMyStructWithCounter(cbsWithCounter);
+}
+
+// Complex struct with multiple resources and arrays - local initialization 
from cbuffer
+void case4() {
+// CHECK: %w1 = alloca %struct.WrappaStruct, align 1
+
+// w1.b - copy from cbuffer
+// CHECK-NEXT: [[W1_B_PTR:%.*]] = getelementptr inbounds %struct.WrappaStruct, 
ptr %w1, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_B:%.*]] = load float, ptr addrspace(2) @cbw, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_B]], ptr [[W1_B_PTR]], align 4
+
+// w1.s
+// CHECK-NEXT: [[W1_S_PTR:%.*]] = getelementptr inbounds %struct.WrappaStruct, 
ptr %w1, i32 0, i32 1
+
+// w1.s[0]
+// CHECK-NEXT: [[W1_S_0_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[W1_S_PTR]], i32 0, i32 0
+
+// w1.s[0].a - copy from cbuffer
+// CHECK-NEXT: [[W1_S_0_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[W1_S_0_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_0_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 16), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_0_A]], ptr [[W1_S_0_A_PTR]], align 4
+  
+// w1.s[0].Buf - use global resource @cbw.s[0].Buf
+// CHECK-NEXT: [[W1_S_0_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[W1_S_0_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.0.Buf, ptr [[W1_S_0_BUF_PTR]], align 4
+
+// w1.s[1]
+// CHECK-NEXT: [[W1_S_1_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[W1_S_PTR]], i32 0, i32 1
+
+// w1.s[1].a - copy from cbuffer
+// CHECK-NEXT: [[W1_S_1_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[W1_S_1_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_1_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 32), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_1_A]], ptr [[W1_S_1_A_PTR]], align 4
+
+// w1.s[1].Buf - use global resource @cbw.s.1.Buf
+// CHECK-NEXT: [[W1_S_1_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[W1_S_1_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.1.Buf, ptr [[W1_S_1_BUF_PTR]], align 4
+
+// w1.BufArray
+// CHECK-NEXT: [[W1_BUFARRAY_PTR:%.*]] = getelementptr inbounds 
%struct.WrappaStruct, ptr %w1, i32 0, i32 2
+
+// w1.BufArray[0] - initialize resource array element from binding with counter
+// CHECK-NEXT: [[W1_BUFARRAY_0_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[W1_BUFARRAY_PTR]], i32 0, i32 0
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[W1_BUFARRAY_0_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 0, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+  
+// w1.BufArray[1] - initialize resource array element from binding with counter
+// CHECK-NEXT: [[W1_BUFARRAY_1_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[W1_BUFARRAY_PTR]], i32 0, i32 1
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[W1_BUFARRAY_1_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+
+  WrappaStruct w1 = cbw;
+}
+
+// Complex struct with multiple resources and arrays - assignment from cbuffer
+// CHECK-LABEL: case5
+void case5() {
+// CHECK: %w2 = alloca %struct.WrappaStruct, align 1
+// CHECK-NEXT: [[TMP:%.*]] = alloca %struct.WrappaStruct, align 1
+
+// w2.b - copy from cbuffer
+// CHECK-NEXT: [[W2_B_PTR:%.*]] = getelementptr inbounds %struct.WrappaStruct, 
ptr %w2, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_B:%.*]] = load float, ptr addrspace(2) @cbw, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_B]], ptr [[W2_B_PTR]], align 4
+
+// w2.s
+// CHECK-NEXT: [[W2_S_PTR:%.*]] = getelementptr inbounds %struct.WrappaStruct, 
ptr %w2, i32 0, i32 1
+
+// w2.s[0]
+// CHECK-NEXT: [[W2_S_0_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[W2_S_PTR]], i32 0, i32 0
+
+// w2.s[0].a - copy from cbuffer
+// CHECK-NEXT: [[W2_S_0_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[W2_S_0_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_0_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 16), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_0_A]], ptr [[W2_S_0_A_PTR]], align 4
+  
+// w2.s[0].Buf - use global resource @cbw.s[0].Buf
+// CHECK-NEXT: [[W2_S_0_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[W2_S_0_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.0.Buf, ptr [[W2_S_0_BUF_PTR]], align 4
+
+// w2.s[1]
+// CHECK-NEXT: [[W2_S_1_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[W2_S_PTR]], i32 0, i32 1
+
+// w2.s[1].a - copy from cbuffer
+// CHECK-NEXT: [[W2_S_1_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[W2_S_1_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_1_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 32), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_1_A]], ptr [[W2_S_1_A_PTR]], align 4
+
+// w2.s[1].Buf - use global resource @cbw.s.1.Buf
+// CHECK-NEXT: [[W2_S_1_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[W2_S_1_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.1.Buf, ptr [[W2_S_1_BUF_PTR]], align 4
+
+// w2.BufArray
+// CHECK-NEXT: [[W2_BUFARRAY_PTR:%.*]] = getelementptr inbounds 
%struct.WrappaStruct, ptr %w2, i32 0, i32 2
+
+// w2.BufArray[0] - initialize resource array element from binding
+// CHECK-NEXT: [[W2_BUFARRAY_0_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[W2_BUFARRAY_PTR]], i32 0, i32 0
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[W2_BUFARRAY_0_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 0, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+
+// w2.BufArray[1] - initialize resource array element from binding with counter
+// CHECK-NEXT: [[W2_BUFARRAY_1_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[W2_BUFARRAY_PTR]], i32 0, i32 1
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[W2_BUFARRAY_1_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+  
+// result of the assignment expression passed along in a temporary
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TMP]], ptr align 
1 %w2, i32 36, i1 false)
+
+  WrappaStruct w2;
+  w2 = cbw;
+}
+
+// Complex struct with multiple resources and arrays - function argument from 
cbuffer
+// CHECK-LABEL: case6
+void case6() {
+// CHECK: [[TMP:%.*]] = alloca %struct.WrappaStruct, align 1
+
+// tmp.b - copy from cbuffer
+// CHECK-NEXT: [[TMP_B_PTR:%.*]] = getelementptr inbounds 
%struct.WrappaStruct, ptr [[TMP]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_B:%.*]] = load float, ptr addrspace(2) @cbw, align 4
+// CHECK-NEXT: store float [[CBUF_LOAD_B]], ptr [[TMP_B_PTR]], align 4
+
+// tmp.s
+// CHECK-NEXT: [[TMP_S_PTR:%.*]] = getelementptr inbounds 
%struct.WrappaStruct, ptr [[TMP]], i32 0, i32 1
+
+// tmp.s[0]
+// CHECK-NEXT: [[TMP_S_0_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[TMP_S_PTR]], i32 0, i32 0
+
+// tmp.s[0].a - copy from cbuffer
+// CHECK-NEXT: [[TMP_S_0_A_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[TMP_S_0_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_0_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 16), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_0_A]], ptr [[TMP_S_0_A_PTR]], align 4
+  
+// tmp.s[0].Buf - use global resource @cbw.s[0].Buf
+// CHECK-NEXT: [[TMP_S_0_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[TMP_S_0_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.0.Buf, ptr [[TMP_S_0_BUF_PTR]], align 4
+
+// tmp.s[1]
+// CHECK-NEXT: [[TMP_S_1_PTR:%.*]] = getelementptr inbounds [2 x 
%struct.MyStruct], ptr [[TMP_S_PTR]], i32 0, i32 1
+
+// tmp.s[1].a - copy from cbuffer
+// CHECK-NEXT: [[TMP_S_1_A_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[TMP_S_1_PTR]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_1_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 32), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_1_A]], ptr [[TMP_S_1_A_PTR]], align 4
+
+// tmp.s[1].Buf - use global resource @cbw.s.1.Buf
+// CHECK-NEXT: [[TMP_S_1_BUF_PTR:%.*]] = getelementptr inbounds 
%struct.MyStruct, ptr [[TMP_S_1_PTR]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.1.Buf, ptr [[TMP_S_1_BUF_PTR]], align 4
+
+// tmp.BufArray
+// CHECK-NEXT: [[TMP_BUFARRAY_PTR:%.*]] = getelementptr inbounds 
%struct.WrappaStruct, ptr [[TMP]], i32 0, i32 2
+
+// tmp.BufArray[0] - initialize resource array element from binding with 
counter
+// CHECK-NEXT: [[TMP_BUFARRAY_0_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[TMP_BUFARRAY_PTR]], i32 0, i32 0
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[TMP_BUFARRAY_0_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 0, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+
+// tmp.BufArray[1] - initialize resource array element from binding with 
counter
+// CHECK-NEXT: [[TMP_BUFARRAY_1_PTR:%.*]] = getelementptr [2 x 
%"class.hlsl::RWStructuredBuffer"], ptr [[TMP_BUFARRAY_PTR]], i32 0, i32 1
+// CHECK-NEXT: call void 
@hlsl::RWStructuredBuffer<int>::__createFromImplicitBindingWithImplicitCounter({{[^)]*}})
+// CHECK-SAME: (ptr {{.*}} sret(%"class.hlsl::RWStructuredBuffer") align 4 
[[TMP_BUFARRAY_1_PTR]],
+// CHECK-SAME:i32 noundef 6, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr 
noundef @cbw.BufArray.str, i32 noundef 7)
+
+// call useWrappaStruct with the temporary
+// CHECK-NEXT: call void @useWrappaStruct(WrappaStruct)(ptr noundef 
dead_on_return [[TMP]])
+
+  useWrappaStruct(cbw);
+}
+
+// Member access in a complex cbuffer struct with resources - local 
initialization
+// CHECK-LABEL: case7
+void case7() {
+// CHECK: %s3 = alloca %struct.MyStruct, align 4
+
+// s3.a - copy from cbuffer (cbw.s[0].a)
+// CHECK-NEXT: [[S3_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr 
%s3, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_0_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 16), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_0_A]], ptr [[S3_A_PTR]], align 4
+
+// s3.Buf - use global resource @cbw.s.0.Buf
+// CHECK-NEXT: [[S3_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr %s3, i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.0.Buf, ptr [[S3_BUF_PTR]], align 4
+  MyStruct s3 = cbw.s[0];
+}
+
+// Member access in a complex cbuffer struct with resources - assignment
+// CHECK-LABEL: case8
+void case8() {
+// CHECK: %s4 = alloca %struct.MyStruct, align 4
+// CHECK: [[TMP:%.*]] = alloca %struct.MyStruct, align 4
+
+// s4.a - copy from cbuffer (cbw.s[1].a)
+// CHECK-NEXT: [[S4_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, ptr 
%s4, i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_1_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 32), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_1_A]], ptr [[S4_A_PTR]], align 4
+
+// s4.Buf - use global resource @cbw.s.1.Buf
+// CHECK-NEXT: [[S4_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr %s4, i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.1.Buf, ptr [[S4_BUF_PTR]], align 4
+
+// result of the assignment expression passed along in a temporary
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[TMP]], ptr align 
4 %s4, i32 8, i1 false)
+
+  MyStruct s4;
+  s4 = cbw.s[1];
+}
+
+// Member access in a complex cbuffer struct with resources - function argument
+// CHECK-LABEL: case9
+void case9() {
+// CHECK: [[TMP:%.*]] = alloca %struct.MyStruct, align 4
+
+// tmp.a - copy from cbuffer (cbw.s[0].a)
+// CHECK-NEXT: [[TMP_A_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[TMP]], i32 0, i32 0
+// CHECK-NEXT: [[CBUF_LOAD_S_0_A:%.*]] = load i32, ptr addrspace(2) 
getelementptr inbounds nuw (i8, ptr addrspace(2) @cbw, i32 16), align 4
+// CHECK-NEXT: store i32 [[CBUF_LOAD_S_0_A]], ptr [[TMP_A_PTR]], align 4
+
+// tmp.Buf - use global resource @cbw.s.0.Buf
+// CHECK-NEXT: [[TMP_BUF_PTR:%.*]] = getelementptr inbounds %struct.MyStruct, 
ptr [[TMP]], i32 0, i32 1
+// CHECK-NEXT: store ptr @cbw.s.0.Buf, ptr [[TMP_BUF_PTR]], align 4
+
+// call useMyStruct with the temporary
+// CHECK-NEXT: call void @useMyStruct(MyStruct)(ptr noundef dead_on_return 
[[TMP]])
+
+  useMyStruct(cbw.s[0]);
+}

>From da5ea4f8b136b0441dc6b7edcd3a821198caf18c Mon Sep 17 00:00:00 2001
From: Helena Kotas <[email protected]>
Date: Thu, 18 Jun 2026 23:37:11 -0700
Subject: [PATCH 3/3] code review feedback

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 1c6407b75ed0f..e2db2f82d48d6 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -105,6 +105,7 @@ static void copyGlobalResource(CodeGenFunction &CGF, const 
VarDecl *ResourceVD,
                                AggValueSlot &DestSlot) {
   GlobalVariable *ResGV =
       cast<GlobalVariable>(CGF.CGM.GetAddrOfGlobalVar(ResourceVD));
+  assert(ResGV && "expected valid global variable");
   CGF.Builder.CreateStore(ResGV, DestSlot.getAddress());
 }
 
@@ -492,7 +493,7 @@ class HLSLBufferCopyEmitter {
   SmallVector<llvm::Value *> CurStoreIndices;
   SmallVector<llvm::Value *> CurLoadIndices;
 
-  using EmitResourceFn = llvm::function_ref<void(AggValueSlot &)>;
+  using EmitResourceFnTy = llvm::function_ref<void(AggValueSlot &)>;
 
   // Creates & returns either a structured.gep or a ptradd/gep depending on
   // langopts.
@@ -536,7 +537,7 @@ class HLSLBufferCopyEmitter {
     return true;
   }
 
-  // Returns true if the type is either a struct represening a resource record,
+  // Returns true if the type is either a struct representing a resource 
record,
   // or an array of structs that are resource records. This assumes a struct is
   // a resource record if the first element is a target type (resource handle).
   // This is the case for all target types used by HLSL except the padding type
@@ -555,7 +556,7 @@ class HLSLBufferCopyEmitter {
   }
 
   void emitResourceOrResourceArray(Value *Dst, llvm::Type *DstTy,
-                                   EmitResourceFn EmitResFn) {
+                                   EmitResourceFnTy EmitResFn) {
     CharUnits DstAlign =
         
CharUnits::fromQuantity(CGF.CGM.getDataLayout().getABITypeAlign(DstTy));
     Address DstAddr(Dst, DstTy, DstAlign);
@@ -568,7 +569,8 @@ class HLSLBufferCopyEmitter {
   }
 
   void emitBufferLayoutCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
-                            llvm::ArrayType *DstTy, EmitResourceFn EmitResFn) {
+                            llvm::ArrayType *DstTy,
+                            EmitResourceFnTy EmitResFn) {
     // Those assumptions are checked by isBufferLayoutArray.
     auto *SrcPaddedArrayTy = cast<llvm::ArrayType>(SrcTy->getElementType(0));
     assert(SrcPaddedArrayTy->getNumElements() + 1 == DstTy->getNumElements());
@@ -596,7 +598,7 @@ class HLSLBufferCopyEmitter {
   }
 
   void emitCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
-                llvm::Type *DstTy, EmitResourceFn EmitResFn) {
+                llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
     assert(!isResourceOrResourceArray(DstTy) &&
            "direct access to resources or resource arrays should be handled "
            "separately");
@@ -645,7 +647,7 @@ class HLSLBufferCopyEmitter {
   }
 
   void emitCopy(Value *Src, llvm::ArrayType *SrcTy, Value *Dst,
-                llvm::Type *DstTy, EmitResourceFn EmitResFn) {
+                llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
     for (unsigned I = 0, E = SrcTy->getNumElements(); I < E; ++I) {
       auto *SrcElt =
           emitAccessChain(SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, I)});
@@ -658,7 +660,7 @@ class HLSLBufferCopyEmitter {
   }
 
   void emitElementCopy(Value *Src, llvm::Type *SrcTy, Value *Dst,
-                       llvm::Type *DstTy, EmitResourceFn EmitResFn) {
+                       llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
     if (auto *AT = dyn_cast<llvm::ArrayType>(SrcTy))
       return emitCopy(Src, AT, Dst, DstTy, EmitResFn);
     if (auto *ST = dyn_cast<llvm::StructType>(SrcTy))
@@ -679,7 +681,7 @@ class HLSLBufferCopyEmitter {
   HLSLBufferCopyEmitter(CodeGenFunction &CGF, Address DstPtr, Address SrcPtr)
       : CGF(CGF), DstPtr(DstPtr), SrcPtr(SrcPtr) {}
 
-  bool emitCopy(QualType CType, EmitResourceFn EmitResFn = nullptr) {
+  bool emitCopy(QualType CType, EmitResourceFnTy EmitResFn = nullptr) {
     LayoutTy = HLSLBufferLayoutBuilder(CGF.CGM).layOutType(CType);
 
     // TODO: We should be able to fall back to a regular memcpy if the layout
@@ -700,6 +702,10 @@ class HLSLBufferCopyEmitter {
 // The resources are always returned in that order, which is the same order
 // we need when a struct is copied element-by-element.
 class AssociatedResourcesList {
+  // Iterator pointers for the associated resource attributes that match the
+  // prefix. Begin = begin of the range of attributes that match the prefix End
+  // = end of the range of attributes that match the prefix Next = the current
+  // attribute in the iteration to be returned by getNextResource
   specific_attr_iterator<HLSLAssociatedResourceDeclAttr> Begin, End, Next;
 
 public:
@@ -722,6 +728,7 @@ class AssociatedResourcesList {
                          ->getName()
                          .starts_with(ResourceNamePrefix))
       End = ++I;
+
     Next = Begin;
   }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to