[llvm-branch-commits] [clang] [HLSL] Implement codegen for copying cbuffer structs with resources (PR #204232)

Helena Kotas via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Jun 16 12:47:46 PDT 2026


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

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

>From edc5175cef29023304d8483cd00caf6a25faf302 Mon Sep 17 00:00:00 2001
From: Helena Kotas <hekotas at microsoft.com>
Date: Tue, 16 Jun 2026 12:44:37 -0700
Subject: [PATCH] [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]);
+}



More information about the llvm-branch-commits mailing list