Skip to content

[HLSL] Add codegen for accessing resource members of a struct#187127

Merged
hekota merged 15 commits into
llvm:mainfrom
hekota:res-in-structs-codegen-member-access
Apr 20, 2026
Merged

[HLSL] Add codegen for accessing resource members of a struct#187127
hekota merged 15 commits into
llvm:mainfrom
hekota:res-in-structs-codegen-member-access

Conversation

@hekota

@hekota hekota commented Mar 17, 2026

Copy link
Copy Markdown
Member

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a MemberExpr of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its HLSLAssociatedResourceDeclAttr attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

hekota added 8 commits March 2, 2026 18:07
For each resource or resource array member of a struct declared
at global scope or inside a cbuffer, create an implicit global
variable of the same resource type. The variable name will be
derived from the struct instance name and the member name.

The new global is associated with the struct declaration using
a new attribute HLSLAssociatedResourceDeclAttr.

Closes llvm#182988
Add binding attributes to global variables that were created for resources embedded in structs.
The binding values are based on `register` annotations and `[[vk::binding]]` attribute on the struct instance.

Depends on llvm#184281

Fixes llvm#182992
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. HLSL HLSL Language Support labels Mar 17, 2026
@llvmbot

llvmbot commented Mar 17, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Helena Kotas (hekota)

Changes

Depends on #184731

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a MemberExpr of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its HLSLAssociatedResourceDeclAttr attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989


Patch is 25.58 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/187127.diff

8 Files Affected:

  • (modified) clang/include/clang/AST/HLSLResource.h (+1)
  • (modified) clang/lib/AST/HLSLResource.cpp (+14)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+13-3)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+130-8)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+2)
  • (added) clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl (+83)
  • (added) clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl (+132)
  • (added) clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl (+53)
diff --git a/clang/include/clang/AST/HLSLResource.h b/clang/include/clang/AST/HLSLResource.h
index a37acb3660d00..aeea3bdac99af 100644
--- a/clang/include/clang/AST/HLSLResource.h
+++ b/clang/include/clang/AST/HLSLResource.h
@@ -128,6 +128,7 @@ class EmbeddedResourceNameBuilder {
   void pushName(llvm::StringRef N) { pushName(N, FieldDelim); }
   void pushBaseName(llvm::StringRef N);
   void pushArrayIndex(uint64_t Index);
+  void pushBaseNameHierarchy(CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD);
 
   void pop() {
     assert(!Offsets.empty() && "no name to pop");
diff --git a/clang/lib/AST/HLSLResource.cpp b/clang/lib/AST/HLSLResource.cpp
index 19321625222f3..4545d94f6a5a9 100644
--- a/clang/lib/AST/HLSLResource.cpp
+++ b/clang/lib/AST/HLSLResource.cpp
@@ -42,5 +42,19 @@ void EmbeddedResourceNameBuilder::pushArrayIndex(uint64_t Index) {
   OS << Index;
 }
 
+void EmbeddedResourceNameBuilder::pushBaseNameHierarchy(
+    CXXRecordDecl *DerivedRD, CXXRecordDecl *BaseRD) {
+  Offsets.push_back(Name.size());
+  Name.append(FieldDelim);
+  while (BaseRD != DerivedRD) {
+    assert(DerivedRD->getNumBases() == 1 &&
+           "HLSL does not support multiple inheritance");
+    DerivedRD = DerivedRD->bases_begin()->getType()->getAsCXXRecordDecl();
+    assert(DerivedRD && "base class not found");
+    Name.append(DerivedRD->getName());
+    Name.append(BaseClassDelim);
+  }
+}
+
 } // namespace hlsl
 } // namespace clang
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index eebb36276e0eb..c1f478b2855db 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5472,10 +5472,20 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
     EmitIgnoredExpr(E->getBase());
     return EmitDeclRefLValue(DRE);
   }
-  if (getLangOpts().HLSL &&
-      E->getType().getAddressSpace() == LangAS::hlsl_constant) {
+
+  if (getLangOpts().HLSL) {
+    QualType QT = E->getType();
     // We have an HLSL buffer - emit using HLSL's layout rules.
-    return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
+    if (QT.getAddressSpace() == LangAS::hlsl_constant)
+      return CGM.getHLSLRuntime().emitBufferMemberExpr(*this, E);
+
+    // Resource or resource array member of a global struct/class
+    if (QT->isHLSLResourceRecord() || QT->isHLSLResourceRecordArray()) {
+      std::optional<LValue> LV;
+      LV = CGM.getHLSLRuntime().emitResourceMemberExpr(*this, E);
+      if (LV.has_value())
+        return *LV;
+    }
   }
 
   Expr *BaseExpr = E->getBase();
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index c1329ede7430f..0e8f4be33df44 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -54,6 +54,7 @@ using namespace clang::hlsl;
 using namespace llvm;
 
 using llvm::hlsl::CBufferRowSizeInBytes;
+using EmbeddedResourceNameBuilder = clang::hlsl::EmbeddedResourceNameBuilder;
 
 namespace {
 
@@ -96,16 +97,105 @@ void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer,
   RootSignatureValMD->addOperand(MDVals);
 }
 
+// Gived 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.
+//
+// For example, for a member access like "myStructArray[0].memberA",
+// this function will find the VarDecl of "myStructArray" and use the
+// EmbeddedResourceNameBuilder to build the resource name
+// "myStructArray.0.memberA".
+static const VarDecl *getStructResourceParentDeclAndBuildName(
+    const MemberExpr *ME, EmbeddedResourceNameBuilder &NameBuilder) {
+
+  SmallVector<const Expr *> WorkList;
+  const VarDecl *VD = nullptr;
+  const Expr *E = ME;
+
+  while (!VD) {
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+      assert(isa<VarDecl>(DRE->getDecl()) &&
+             "member expr base is not a var decl");
+      VD = cast<VarDecl>(DRE->getDecl());
+      NameBuilder.pushName(VD->getName());
+      break;
+    }
+
+    WorkList.push_back(E);
+    if (const auto *ME = dyn_cast<MemberExpr>(E))
+      E = ME->getBase();
+    else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
+      E = ICE->getSubExpr();
+    else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
+      E = ASE->getBase();
+    else
+      llvm_unreachable("unexpected expr type in resource member access");
+  }
+
+  while (!WorkList.empty()) {
+    E = WorkList.pop_back_val();
+    if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+      NameBuilder.pushName(
+          ME->getMemberNameInfo().getName().getAsIdentifierInfo()->getName());
+    } else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E)) {
+      if (ICE->getCastKind() == CK_UncheckedDerivedToBase) {
+        CXXRecordDecl *DerivedRD =
+            ICE->getSubExpr()->getType()->getAsCXXRecordDecl();
+        CXXRecordDecl *BaseRD = ICE->getType()->getAsCXXRecordDecl();
+        NameBuilder.pushBaseNameHierarchy(DerivedRD, BaseRD);
+      }
+    } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
+      const Expr *IdxExpr = ASE->getIdx();
+      std::optional<llvm::APSInt> Value =
+          IdxExpr->getIntegerConstantExpr(VD->getASTContext());
+      assert(Value &&
+             "expected constant index in struct with resource array access");
+      NameBuilder.pushArrayIndex(Value->getZExtValue());
+    } else {
+      llvm_unreachable("unexpected expr type in resource member access");
+    }
+  }
+  return VD;
+}
+
+// Given a MemberExpr of a resource or resource array type, find the
+// corresponding global resource declaration associated with the owning struct
+// or class instance via HLSLAssociatedResourceDeclAttr.
+static const VarDecl *
+findAssociatedResourceDeclForStruct(ASTContext &AST, const MemberExpr *ME) {
+
+  EmbeddedResourceNameBuilder NameBuilder;
+  const VarDecl *ParentVD =
+      getStructResourceParentDeclAndBuildName(ME, NameBuilder);
+
+  if (!ParentVD->hasGlobalStorage())
+    return nullptr;
+
+  IdentifierInfo *II = NameBuilder.getNameAsIdentifier(AST);
+  for (const Attr *A : ParentVD->getAttrs()) {
+    const auto *ADA = dyn_cast<HLSLAssociatedResourceDeclAttr>(A);
+    if (!ADA)
+      continue;
+    VarDecl *AssocResVD = dyn_cast<VarDecl>(ADA->getResDecl());
+    if (AssocResVD->getIdentifier() == II)
+      return AssocResVD;
+  }
+  return nullptr;
+}
+
 // Find array variable declaration from DeclRef expression
-static const ValueDecl *getArrayDecl(const Expr *E) {
-  if (const DeclRefExpr *DRE =
-          dyn_cast_or_null<DeclRefExpr>(E->IgnoreImpCasts()))
+static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) {
+  E = E->IgnoreImpCasts();
+  if (const auto *DRE = dyn_cast_or_null<DeclRefExpr>(E))
     return DRE->getDecl();
+  if (isa<MemberExpr>(E))
+    return findAssociatedResourceDeclForStruct(AST, cast<MemberExpr>(E));
   return nullptr;
 }
 
 // Find array variable declaration from nested array subscript AST nodes
-static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
+static const ValueDecl *getArrayDecl(ASTContext &AST,
+                                     const ArraySubscriptExpr *ASE) {
   const Expr *E = nullptr;
   while (ASE != nullptr) {
     E = ASE->getBase()->IgnoreImpCasts();
@@ -113,7 +203,7 @@ static const ValueDecl *getArrayDecl(const ArraySubscriptExpr *ASE) {
       return nullptr;
     ASE = dyn_cast<ArraySubscriptExpr>(E);
   }
-  return getArrayDecl(E);
+  return getArrayDecl(AST, E);
 }
 
 // Get the total size of the array, or -1 if the array is unbounded.
@@ -1227,8 +1317,8 @@ std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
   // Let clang codegen handle local and static resource array subscripts,
   // or when the subscript references on opaque expression (as part of
   // ArrayInitLoopExpr AST node).
-  const VarDecl *ArrayDecl =
-      dyn_cast_or_null<VarDecl>(getArrayDecl(ArraySubsExpr));
+  const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(
+      getArrayDecl(CGF.CGM.getContext(), ArraySubsExpr));
   if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
       ArrayDecl->getStorageClass() == SC_Static)
     return std::nullopt;
@@ -1325,7 +1415,8 @@ bool CGHLSLRuntime::emitResourceArrayCopy(LValue &LHS, Expr *RHSExpr,
   assert(ResultTy->isHLSLResourceRecordArray() && "expected resource array");
 
   // Let Clang codegen handle local and static resource array copies.
-  const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(getArrayDecl(RHSExpr));
+  const VarDecl *ArrayDecl =
+      dyn_cast_or_null<VarDecl>(getArrayDecl(CGF.CGM.getContext(), RHSExpr));
   if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
       ArrayDecl->getStorageClass() == SC_Static)
     return false;
@@ -1448,6 +1539,37 @@ std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr(
   return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
 }
 
+std::optional<LValue>
+CGHLSLRuntime::emitResourceMemberExpr(CodeGenFunction &CGF,
+                                      const MemberExpr *ME) {
+  assert((ME->getType()->isHLSLResourceRecord() ||
+          ME->getType()->isHLSLResourceRecordArray()) &&
+         "expected resource member expression");
+
+  if (ME->getType()->isHLSLResourceRecordArray()) {
+    // FIXME: Handle member access of the whole array of resources
+    // (llvm/llvm-project#187087). Access to individual resource array elements
+    // is already handled in emitResourceArraySubscriptExpr.
+    return std::nullopt;
+  }
+
+  const VarDecl *ResourceVD =
+      findAssociatedResourceDeclForStruct(CGF.CGM.getContext(), ME);
+  if (!ResourceVD)
+    return std::nullopt;
+
+  GlobalVariable *ResGV =
+      cast<GlobalVariable>(CGM.GetAddrOfGlobalVar(ResourceVD));
+  const DataLayout &DL = CGM.getDataLayout();
+  llvm::Type *Ty = ResGV->getValueType();
+  CharUnits Align = CharUnits::fromQuantity(DL.getABITypeAlign(Ty));
+  Address Addr = Address(ResGV, Ty, Align);
+  LValue LV = LValue::MakeAddr(Addr, ME->getType(), CGM.getContext(),
+                               LValueBaseInfo(AlignmentSource::Type),
+                               CGM.getTBAAAccessInfo(ME->getType()));
+  return LV;
+}
+
 namespace {
 /// Utility for emitting copies following the HLSL buffer layout rules (ie,
 /// copying out of a cbuffer).
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 466c809fdef78..1977dad693b52 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -291,6 +291,8 @@ class CGHLSLRuntime {
                       QualType CType);
 
   LValue emitBufferMemberExpr(CodeGenFunction &CGF, const MemberExpr *E);
+  std::optional<LValue> emitResourceMemberExpr(CodeGenFunction &CGF,
+                                               const MemberExpr *E);
 
 private:
   void emitBufferGlobalsAndMetadata(const HLSLBufferDecl *BufDecl,
diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
new file mode 100644
index 0000000000000..283d3616aa660
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-array.hlsl
@@ -0,0 +1,83 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
+
+// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) }
+
+// Array of structs with resources
+struct A {
+  RWBuffer<float> Buf;
+};
+
+// CHECK: @arrayOfA.0.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[arrayOfA0BufStr:.*]] = private unnamed_addr constant [15 x i8] c"arrayOfA.0.Buf\00"
+// CHECK: @arrayOfA.1.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[arrayOfA1BufStr:.*]] = private unnamed_addr constant [15 x i8] c"arrayOfA.1.Buf\00"
+
+[[vk::binding(0, 1)]]
+A arrayOfA[2] : register(u0, space1);
+
+// Nested struct arrays with resources
+struct G {
+  A multiArray[2][2];
+};
+
+// CHECK: @gArray.0.multiArray.0.0.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray0MultiArray00BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.0.0.Buf\00"
+// CHECK: @gArray.0.multiArray.0.1.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray0MultiArray01BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.0.1.Buf\00"
+// CHECK: @gArray.0.multiArray.1.0.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray0MultiArray10BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.1.0.Buf\00"
+// CHECK: @gArray.0.multiArray.1.1.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray0MultiArray11BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.0.multiArray.1.1.Buf\00"
+// CHECK: @gArray.1.multiArray.0.0.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray1MultiArray00BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.0.0.Buf\00"
+// CHECK: @gArray.1.multiArray.0.1.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray1MultiArray01BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.0.1.Buf\00"
+// CHECK: @gArray.1.multiArray.1.0.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray1MultiArray10BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.1.0.Buf\00"
+// CHECK: @gArray.1.multiArray.1.1.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[gArray1MultiArray11BufStr:.*]] = private unnamed_addr constant [28 x i8] c"gArray.1.multiArray.1.1.Buf\00"
+
+// Make sure they are initialized from binding
+//
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @arrayOfA.0.Buf,
+// CHECK-SAME: i32 noundef 0, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @[[arrayOfA0BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @arrayOfA.1.Buf,
+// CHECK-SAME: i32 noundef 1, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @[[arrayOfA1BufStr]])
+//
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.0.0.Buf,
+// CHECK-SAME: i32 noundef 10, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray00BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.0.1.Buf,
+// CHECK-SAME: i32 noundef 11, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray01BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.1.0.Buf,
+// CHECK-SAME: i32 noundef 12, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray10BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.0.multiArray.1.1.Buf,
+// CHECK-SAME: i32 noundef 13, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray0MultiArray11BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.0.0.Buf,
+// CHECK-SAME: i32 noundef 14, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray00BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.0.1.Buf,
+// CHECK-SAME: i32 noundef 15, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray01BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.1.0.Buf,
+// CHECK-SAME: i32 noundef 16, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray10BufStr]])
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @gArray.1.multiArray.1.1.Buf,
+// CHECK-SAME: i32 noundef 17, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @[[gArray1MultiArray11BufStr]])
+
+[[vk::binding(10, 2)]]
+G gArray[2] : register(u10, space2);
+
+// CHECK: define internal void @main()()
+// CHECK-NEXT: entry:
+[numthreads(1, 1, 1)]
+void main() {
+
+// CHECK-NEXT: %[[PTR1:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} @arrayOfA.1.Buf, i32 noundef 0)
+// CHECK-NEXT: store float 1.000000e+00, ptr %[[PTR1]]
+  arrayOfA[1].Buf[0] = 1.0f;
+
+// CHECK-NEXT: %[[PTR2:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} @gArray.1.multiArray.1.0.Buf, i32 noundef 0)
+// CHECK-NEXT: store float 2.000000e+00, ptr %[[PTR2]]
+  gArray[1].multiArray[1][0].Buf[0] = 2.0f;
+
+// CHECK-NEXT: %[[PTR3:.*]] = call {{.*}} ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr {{.*}} @gArray.0.multiArray.0.1.Buf, i32 noundef 0)
+// CHECK-NEXT: store float 3.000000e+00, ptr %[[PTR3]]
+  gArray[0].multiArray[0][1].Buf[0] = 3.0f;
+}
diff --git a/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl b/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
new file mode 100644
index 0000000000000..5b5b8270f8e86
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
@@ -0,0 +1,132 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
+
+// CHECK: %"class.hlsl::RWBuffer" = type { target("dx.TypedBuffer", float, 1, 0, 0) }
+// CHECK: %"class.hlsl::StructuredBuffer" = type { target("dx.RawBuffer", float, 0, 0) }
+// CHECK: %"class.hlsl::SamplerState" = type { target("dx.Sampler", 0) }
+// CHECK: %"class.hlsl::StructuredBuffer.0" = type { target("dx.RawBuffer", i32, 0, 0) }
+
+// Simple inheritance
+struct A {
+  RWBuffer<float> Buf;
+};
+
+struct C : A {
+  RWBuffer<float> Buf2;
+};
+
+// Global variables for resources c.A::Buf and c.Buf2
+// (Looks like llvm-cxxfilt doesn't demangle names with `::`.)
+//
+// CHECK: @"_ZL8c.A::Buf" = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[cABufStr:.*]] = private unnamed_addr constant [9 x i8] c"c.A::Buf\00"
+// CHECK: @c.Buf2 = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[cBuf2Str:.*]] = private unnamed_addr constant [7 x i8] c"c.Buf2\00"
+
+[[vk::binding(3)]]
+C c : register(u3);
+
+// Global variables for resources d.A::Buf and d.A.Buf
+//
+// CHECK: @"_ZL8d.A::Buf" = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[dABufStr1:.*]] = private unnamed_addr constant [9 x i8] c"d.A::Buf\00"
+// CHECK: @d.A.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[dABufStr2:.*]] = private unnamed_addr constant [8 x i8] c"d.A.Buf\00"
+
+// Inheritance with same named field
+struct D : A {
+    A A;
+};
+D d;
+
+// Multiple resources kinds and inheritance
+class B {
+  StructuredBuffer<int> SrvBufs[2];
+};
+
+class E : B {
+};
+
+class F : E {
+  A a;
+  StructuredBuffer<float> SrvBuf;
+  SamplerState Samp;
+};
+
+// Global variables for resources f.a.Buf, f.SrvBuf and f.Samp.
+// Resource array f.E::B::SrvBufs does not have a global, it is initialized on demand.
+//
+// CHECK: @f.a.Buf = internal global %"class.hlsl::RWBuffer" poison
+// CHECK: @[[fABufStr:.*]] = private unnamed_addr constant [8 x i8] c"f.a.Buf\00"
+// CHECK: @f.SrvBuf = internal global %"class.hlsl::StructuredBuffer" poison
+// CHECK: @[[fSrvBufStr:.*]] = private unnamed_addr constant [9 x i8] c"f.SrvBuf\00"
+// CHECK: @f.Samp = internal global %"class.hlsl::SamplerState" poison
+// CHECK: @[[fSampStr:.*]] = private unnamed_addr constant [7 x i8] c"f.Samp\00"
+// CHECK: @[[fEBSrvBufStr:.*]] = private unnamed_addr constant [16 x i8] c"f.E::B::SrvBufs\00"
+
+[[vk::binding(10)]]
+F f : register(t0) : register(u20) : register(s3);
+
+// Make sure they are initialized from binding
+//
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @"_ZL8c.A::Buf",
+// CHECK-SAME: i32 noundef 3, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[cABufStr]])
+
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromBinding({{.*}})(ptr {{.*}} @c.Buf2,
+// CHECK-SAME: i32 noundef 4, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @[[cBuf2Str]])
+
+// CHECK: call void @hlsl::RWBuffer<float>::__createFromImplicitBinding({{.*}})(ptr {{.*}} @"_ZL8d.A::Buf",
+// CHECK-SAME: i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 ...
[truncated]

@hekota hekota linked an issue Mar 17, 2026 that may be closed by this pull request
@hekota hekota changed the title [HLSL] Implement CodeGen for accessing resource members of a struct [HLSL] Add codegen for accessing resource members of a struct Mar 18, 2026
Comment thread clang/lib/CodeGen/CGExpr.cpp Outdated
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated
hekota added 2 commits March 31, 2026 10:35
Also prevent crash when accessing a resource member inside a member function. This will be implemented later.
@hekota hekota changed the base branch from users/hekota/pr184731-res-in-structs-sema-with-binding to main April 3, 2026 01:57
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 115485 tests passed
  • 4603 tests skipped

✅ The build succeeded and all tests passed.

Comment thread clang/lib/AST/HLSLResource.cpp
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated
Comment thread clang/lib/CodeGen/CGHLSLRuntime.cpp Outdated

@bob80905 bob80905 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

const HLSLAttributedResourceType *ResHandleTy = nullptr;
if (const auto *AT = dyn_cast<ArrayType>(ResTy.getTypePtr())) {
const Type *SingleResTy = ResTy.getTypePtr()->getUnqualifiedDesugaredType();
while (const auto *AT = dyn_cast<ArrayType>(SingleResTy)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this work for arrays of structs containing members that are arrays of resources? Is that a case we care about in this PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would. I have added a test case for this in resources-in-structs-array.hlsl.

@hekota hekota merged commit 944f382 into llvm:main Apr 20, 2026
10 of 11 checks passed
@llvm-ci

llvm-ci commented Apr 20, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder intel-sycl-gpu running on intel-sycl-gpu-01 while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/225/builds/6230

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang :: CodeGenHLSL/resources/resources-in-structs.hlsl' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/bin/clang -cc1 -internal-isystem /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl | llvm-cxxfilt | /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/bin/FileCheck /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# executed command: /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/bin/clang -cc1 -internal-isystem /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# executed command: llvm-cxxfilt
# executed command: /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/build/bin/FileCheck /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# .---command stderr------------
# | /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl:44:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)
# |                ^
# | <stdin>:26:2: note: 'next' match was here
# |  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ]
# |  ^
# | <stdin>:24:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:25:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: /home/test-user/llvm-buildbot-worker/intel-sycl-gpu/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |         21:  
# |         22: ; Function Attrs: alwaysinline convergent nounwind 
# |         23: define internal void @__cxx_global_var_init() #0 { 
# |         24: entry: 
# |         25:  %0 = call token @llvm.experimental.convergence.entry() 
# |         26:  call void @hlsl::RWBuffer<float>::__createFromBinding(unsigned int, unsigned int, int, unsigned int, char const*)(ptr dead_on_unwind writable sret(%"class.hlsl::RWBuffer") align 4 @a.Buf, i32 noundef 0, i32 noundef 0, i32 noundef 1, i32 noundef 0, ptr noundef @.str) #5 [ "convergencectrl"(token %0) ] 
# | next:44      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                                                              error: match on wrong line
# |         27:  ret void 
# |         28: } 
# |         29:  
# |         30: ; Function Attrs: convergent nocallback nofree nosync nounwind willreturn memory(none) 
# |         31: declare token @llvm.experimental.convergence.entry() #1 
# |          .
# |          .
# |          .
# | >>>>>>
...

@llvm-ci

llvm-ci commented Apr 20, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-sie-ubuntu-fast running on sie-linux-worker while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/144/builds/52072

Here is the relevant piece of the build log for the reference
Step 6 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'Clang :: CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl | llvm-cxxfilt | /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/FileCheck /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# executed command: /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/clang -cc1 -internal-isystem /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/lib/clang/23/include -nostdsysteminc -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# note: command had no output on stdout or stderr
# executed command: llvm-cxxfilt
# note: command had no output on stdout or stderr
# executed command: /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/build/bin/FileCheck /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# .---command stderr------------
# | /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl:96:16: error: CHECK-NEXT: is not on the line after the previous match
# | // CHECK-NEXT: %i = alloca i32
# |                ^
# | <stdin>:273:2: note: 'next' match was here
# |  %i = alloca i32, align 4
# |  ^
# | <stdin>:271:7: note: previous match ended here
# | entry:
# |       ^
# | <stdin>:272:1: note: non-matching line after previous match is here
# |  %0 = call token @llvm.experimental.convergence.entry()
# | ^
# | 
# | Input file: <stdin>
# | Check file: /home/buildbot/buildbot-root/llvm-clang-x86_64-sie-ubuntu-fast/llvm-project/clang/test/CodeGenHLSL/resources/resources-in-structs-inheritance.hlsl
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |          .
# |          .
# |          .
# |        268:  
# |        269: ; Function Attrs: alwaysinline convergent mustprogress norecurse nounwind 
# |        270: define internal void @main()() #2 { 
# |        271: entry: 
# |        272:  %0 = call token @llvm.experimental.convergence.entry() 
# |        273:  %i = alloca i32, align 4 
# | next:96      !~~~~~~~~~~~~~~           error: match on wrong line
# |        274:  %tmp = alloca %"class.hlsl::StructuredBuffer.0", align 4 
# |        275:  %a = alloca float, align 4 
# |        276:  %call1 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @"_ZL8c.A::Buf", i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |        277:  store float 0x3FF3AE1480000000, ptr %call1, align 4 
# |        278:  %call2 = call noundef nonnull align 4 dereferenceable(4) ptr @hlsl::RWBuffer<float>::operator[](unsigned int)(ptr noundef nonnull align 4 dereferenceable(4) @c.Buf2, i32 noundef 0) #5 [ "convergencectrl"(token %0) ] 
# |          .
# |          .
...

@ceseo

ceseo commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

This is also breaking AArch64: https://lab.llvm.org/buildbot/#/builders/65/builds/33265

@Kewen12

Kewen12 commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Hi! This PR also breaks our bot: https://lab.llvm.org/buildbot/#/builders/10/builds/26843 Could you please fix or revert to unblock? Thanks!

@boomanaiden154

Copy link
Copy Markdown
Contributor

I've reverted this in 4f57bd2 given this broke premerge as well. Seems like this might have been a mid-air collision.

s-perron pushed a commit to s-perron/llvm-project that referenced this pull request Apr 21, 2026
…87127)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration
is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes llvm#182989
hekota added a commit that referenced this pull request Apr 23, 2026
…rge attempt) (#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

This is the second try to land this. The [first one](#187127 with #188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Apr 23, 2026
…uct (2nd merge attempt) (#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

This is the second try to land this. The [first one](llvm/llvm-project#187127 with #188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
cpullvm-upstream-sync Bot pushed a commit to navaneethshan/cpullvm-toolchain-1 that referenced this pull request Apr 23, 2026
…uct (2nd merge attempt) (#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

This is the second try to land this. The [first one](llvm/llvm-project#187127 with #188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Apr 24, 2026
…uct (2nd merge attempt) (#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes #182989

This is the second try to land this. The [first one](llvm/llvm-project#187127 with #188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
yingopq pushed a commit to yingopq/llvm-project that referenced this pull request Apr 29, 2026
…rge attempt) (llvm#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes llvm#182989

This is the second try to land this. The [first one](llvm#187127 with llvm#188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…87127)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration
is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes llvm#182989
KHicketts pushed a commit to KHicketts/llvm-project that referenced this pull request Apr 30, 2026
…rge attempt) (llvm#193584)

Any expression that accesses a resource or resource array member of a global struct instance must be during codegen replaced by an access of the corresponding implicit global resource variable.

When codegen encounters a `MemberExpr` of a resource type, it traverses the AST to locate the parent struct declaration, building the expected global resource variable name along the way. If the parent declaration is a non-static global struct instance, codegen searches its `HLSLAssociatedResourceDeclAttr` attributes to locate the matching global resource variable and then generates IR code to access the resource global in place of the member access.

Fixes llvm#182989

This is the second try to land this. The [first one](llvm#187127 with llvm#188792 and both PRs had to be reverted. No updates needed to this change. I synced with @inbelic and we agreed
that this one should go in first.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[HLSL] Codegen for accessing resource members of a struct

8 participants