[clang] [llvm] [HLSL] Implement ConstantBuffer<T> with transparent member access (PR #193237)

Steven Perron via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 28 08:33:45 PDT 2026


https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/193237

>From a8e346d23558c1cf6d636bbbef5ea3730849229e Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Wed, 8 Apr 2026 15:00:16 -0400
Subject: [PATCH 1/3] [HLSL] Implement ConstantBuffer<T> with transparent
 member access

This patch implements the ConstantBuffer<T> resource type in Clang.
ConstantBuffer<T> provides transparent access to the members of its
underlying type T, allowing it to be used as if it were an instance of T.

Implementation details:
- ConstantBuffer<T> is defined as a builtin template in HLSLExternalSemaSource.
- Member accesses on ConstantBuffer<T> are intercepted in Sema and
  transformed into calls to an implicit conversion operator. This operator
  returns a reference to T in the hlsl_constant address space.
- Qualifiers::isTargetAddressSpaceSupersetOf is updated to allow implicit
  conversions from HLSL-specific address spaces (like hlsl_constant) to the
  default address space, enabling member function calls on the underlying type.
- __builtin_hlsl_resource_getpointer is introduced to resolve resources to
  memory locations, lowering to target-specific intrinsics
  (int_dx_resource_getpointer or int_spv_resource_getpointer).
- DXIL and SPIR-V backends are updated to handle the new resource access
  pattern.
- Sema validation ensures the template argument T is a compatible struct
  or class.
- Comprehensive tests cover AST representation, Sema validation, and
  CodeGen for both DXIL and SPIR-V targets.

Assisted-by: Gemini
---
 clang/include/clang/Basic/TokenKinds.def      |   1 +
 clang/include/clang/Sema/SemaHLSL.h           |   1 +
 clang/lib/AST/Type.cpp                        |   1 +
 clang/lib/CodeGen/CGHLSLBuiltins.cpp          |  14 +-
 clang/lib/CodeGen/CGHLSLRuntime.h             |   2 +
 clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp |  29 +++-
 clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h   |   1 +
 clang/lib/Sema/HLSLExternalSemaSource.cpp     |  55 +++++-
 clang/lib/Sema/SemaExprMember.cpp             |  52 ++++++
 clang/lib/Sema/SemaHLSL.cpp                   |  47 +++++-
 clang/lib/Sema/SemaTypeTraits.cpp             |   9 +
 clang/test/AST/HLSL/ConstantBuffers-AST.hlsl  | 156 ++++++++++++++++++
 .../CodeGenHLSL/builtins/ConstantBuffer.hlsl  |  92 +++++++++++
 .../BuiltIns/ConstantBuffer-member-funcs.hlsl |  26 +++
 .../BuiltIns/ConstantBuffer-template.hlsl     |  22 +++
 .../SemaHLSL/BuiltIns/ConstantBuffers.hlsl    |  62 +++++++
 .../BuiltIns/resource_getpointer-errors.hlsl  |   7 +-
 llvm/include/llvm/IR/IntrinsicsDirectX.td     |   4 +
 llvm/include/llvm/IR/IntrinsicsSPIRV.td       |   4 +
 llvm/lib/Target/DirectX/DXILMemIntrinsics.cpp |   3 +-
 llvm/lib/Target/DirectX/DXILOpLowering.cpp    |   1 +
 .../lib/Target/DirectX/DXILResourceAccess.cpp |  37 +++--
 llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp |  19 ++-
 .../Target/SPIRV/SPIRVInstructionSelector.cpp |  30 ++--
 24 files changed, 628 insertions(+), 47 deletions(-)
 create mode 100644 clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
 create mode 100644 clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl

diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index dcdd6305f2eca..675154840cfae 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -689,6 +689,7 @@ KEYWORD(out                         , KEYHLSL)
 TYPE_TRAIT_2(__builtin_hlsl_is_scalarized_layout_compatible, IsScalarizedLayoutCompatible, KEYHLSL)
 TYPE_TRAIT_1(__builtin_hlsl_is_intangible, IsIntangibleType, KEYHLSL)
 TYPE_TRAIT_1(__builtin_hlsl_is_typed_resource_element_compatible, IsTypedResourceElementCompatible, KEYHLSL)
+TYPE_TRAIT_1(__builtin_hlsl_is_constant_buffer_element_compatible, IsConstantBufferElementCompatible, KEYHLSL)
 
 // OpenMP Type Traits
 UNARY_EXPR_OR_TYPE_TRAIT(__builtin_omp_required_simd_align, OpenMPRequiredSimdAlign, KEYALL)
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index edb7a8202a80b..5a28dfaad3ea2 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -202,6 +202,7 @@ class SemaHLSL : public SemaBase {
   // HLSL Type trait implementations
   bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
   bool IsTypedResourceElementCompatible(QualType T1);
+  bool IsConstantBufferElementCompatible(QualType T1);
 
   bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6c295c1a9c409..81a4ed2f3c162 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -100,6 +100,7 @@ bool Qualifiers::isTargetAddressSpaceSupersetOf(LangAS A, LangAS B,
          // address spaces to default to work around this problem.
          (A == LangAS::Default && B == LangAS::hlsl_private) ||
          (A == LangAS::Default && B == LangAS::hlsl_device) ||
+         (A == LangAS::Default && B == LangAS::hlsl_constant) ||
          (A == LangAS::Default && B == LangAS::hlsl_input) ||
          (A == LangAS::Default && B == LangAS::hlsl_output) ||
          (A == LangAS::Default && B == LangAS::hlsl_push_constant) ||
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index b82a237ecefca..260d17e30c0e0 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -571,12 +571,20 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
   case Builtin::BI__builtin_hlsl_resource_getpointer:
   case Builtin::BI__builtin_hlsl_resource_getpointer_typed: {
     Value *HandleOp = EmitScalarExpr(E->getArg(0));
-    Value *IndexOp = EmitScalarExpr(E->getArg(1));
+    bool IsIndexed = BuiltinID == Builtin::BI__builtin_hlsl_resource_getpointer
+                         ? E->getNumArgs() > 1
+                         : E->getNumArgs() > 2;
 
     llvm::Type *RetTy = ConvertType(E->getType());
+    if (IsIndexed) {
+      Value *IndexOp = EmitScalarExpr(E->getArg(1));
+      return Builder.CreateIntrinsic(
+          RetTy, CGM.getHLSLRuntime().getCreateResourceGetPointerIntrinsic(),
+          ArrayRef<Value *>{HandleOp, IndexOp});
+    }
     return Builder.CreateIntrinsic(
-        RetTy, CGM.getHLSLRuntime().getCreateResourceGetPointerIntrinsic(),
-        ArrayRef<Value *>{HandleOp, IndexOp});
+        RetTy, CGM.getHLSLRuntime().getCreateResourceGetBasePointerIntrinsic(),
+        ArrayRef<Value *>{HandleOp});
   }
   case Builtin::BI__builtin_hlsl_resource_sample: {
     Value *HandleOp = EmitScalarExpr(E->getArg(0));
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index b54cbab906056..d7ac2346f2428 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -167,6 +167,8 @@ class CGHLSLRuntime {
   GENERATE_HLSL_INTRINSIC_FUNCTION(SClamp, sclamp)
   GENERATE_HLSL_INTRINSIC_FUNCTION(UClamp, uclamp)
 
+  GENERATE_HLSL_INTRINSIC_FUNCTION(CreateResourceGetBasePointer,
+                                   resource_getbasepointer)
   GENERATE_HLSL_INTRINSIC_FUNCTION(CreateResourceGetPointer,
                                    resource_getpointer)
   GENERATE_HLSL_INTRINSIC_FUNCTION(Sample, resource_sample)
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 4c8b0283b0f1e..60810efdd2932 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -554,6 +554,11 @@ void BuiltinTypeMethodBuilder::createDecl() {
         AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo,
         ExplicitSpecifier(), false, /*IsInline=*/true, false,
         ConstexprSpecKind::Unspecified);
+  else if (Name.getNameKind() == DeclarationName::CXXConversionFunctionName)
+    Method = CXXConversionDecl::Create(
+        AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo,
+        false, /*isInline=*/true, ExplicitSpecifier(),
+        ConstexprSpecKind::Unspecified, SourceLocation());
   else
     Method = CXXMethodDecl::Create(
         AST, DeclBuilder.Record, SourceLocation(), NameInfo, FuncTy, TSInfo, SC,
@@ -879,7 +884,7 @@ BuiltinTypeMethodBuilder &BuiltinTypeMethodBuilder::returnValue(T ReturnValue) {
   ASTContext &AST = DeclBuilder.SemaRef.getASTContext();
 
   QualType Ty = ReturnValueExpr->getType();
-  if (Ty->isRecordType()) {
+  if (Ty->isRecordType() && !Method->getReturnType()->isReferenceType()) {
     // For record types, create a call to copy constructor to ensure proper copy
     // semantics.
     auto *ICE =
@@ -1055,6 +1060,28 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSamplerHandle() {
   return *this;
 }
 
+BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addConversionToType() {
+  assert(!Record->isCompleteDefinition() && "record is already complete");
+  ASTContext &AST = SemaRef.getASTContext();
+  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
+
+  QualType ElemTy = getHandleElementType();
+  QualType AddrSpaceElemTy = AST.getCanonicalType(
+      AST.getAddrSpaceQualType(ElemTy, LangAS::hlsl_constant));
+  QualType ReturnTy =
+      AST.getCanonicalType(AST.getLValueReferenceType(AddrSpaceElemTy));
+
+  DeclarationName Name = AST.DeclarationNames.getCXXConversionFunctionName(
+      AST.getCanonicalType(ReturnTy));
+
+  return BuiltinTypeMethodBuilder(*this, Name, ReturnTy, /*IsConst=*/true)
+      .callBuiltin("__builtin_hlsl_resource_getpointer",
+                   AST.getPointerType(AddrSpaceElemTy), PH::Handle)
+      .dereference(PH::LastStmt)
+      .returnValue(PH::LastStmt)
+      .finalize();
+}
+
 BuiltinTypeDeclBuilder &
 BuiltinTypeDeclBuilder::addFriend(CXXRecordDecl *Friend) {
   assert(!Record->isCompleteDefinition() && "record is already complete");
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index 1ffe5d9a624ef..7b05cb0e9b17f 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -83,6 +83,7 @@ class BuiltinTypeDeclBuilder {
   addTextureHandle(ResourceClass RC, bool IsROV, ResourceDimension RD,
                    AccessSpecifier Access = AccessSpecifier::AS_private);
   BuiltinTypeDeclBuilder &addSamplerHandle();
+  BuiltinTypeDeclBuilder &addConversionToType();
   BuiltinTypeDeclBuilder &addArraySubscriptOperators(
       ResourceDimension Dim = ResourceDimension::Unknown);
 
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 235ede8eb0bf0..69b0d420a4f1d 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -369,6 +369,28 @@ static Expr *constructTypedBufferConstraintExpr(Sema &S, SourceLocation NameLoc,
 // this concept:
 // template<typename T> concept is_structured_resource_element_compatible =
 // !__is_intangible<T> && sizeof(T) >= 1;
+static Expr *constructConstantBufferConstraintExpr(Sema &S,
+                                                   SourceLocation NameLoc,
+                                                   TemplateTypeParmDecl *T) {
+  ASTContext &Context = S.getASTContext();
+
+  // Obtain the QualType for 'bool'
+  QualType BoolTy = Context.BoolTy;
+
+  // Create a QualType that points to this TemplateTypeParmDecl
+  QualType TType = Context.getTypeDeclType(T);
+
+  // Create a TypeSourceInfo for the template type parameter 'T'
+  TypeSourceInfo *TTypeSourceInfo =
+      Context.getTrivialTypeSourceInfo(TType, NameLoc);
+
+  TypeTraitExpr *ResExpr = TypeTraitExpr::Create(
+      Context, BoolTy, NameLoc, UTT_IsConstantBufferElementCompatible,
+      {TTypeSourceInfo}, NameLoc, true);
+
+  return ResExpr;
+}
+
 static Expr *constructStructuredBufferConstraintExpr(Sema &S,
                                                      SourceLocation NameLoc,
                                                      TemplateTypeParmDecl *T) {
@@ -415,8 +437,10 @@ static Expr *constructStructuredBufferConstraintExpr(Sema &S,
   return CombinedExpr;
 }
 
+enum class HLSLBufferType { Typed, Structured, Constant };
+
 static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD,
-                                               bool isTypedBuffer) {
+                                               HLSLBufferType BT) {
   ASTContext &Context = S.getASTContext();
   DeclContext *DC = NSD->getDeclContext();
   SourceLocation DeclLoc = SourceLocation();
@@ -440,14 +464,22 @@ static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD,
   DeclarationName DeclName;
   Expr *ConstraintExpr = nullptr;
 
-  if (isTypedBuffer) {
+  switch (BT) {
+  case HLSLBufferType::Typed:
     DeclName = DeclarationName(
         &Context.Idents.get("__is_typed_resource_element_compatible"));
     ConstraintExpr = constructTypedBufferConstraintExpr(S, DeclLoc, T);
-  } else {
+    break;
+  case HLSLBufferType::Structured:
     DeclName = DeclarationName(
         &Context.Idents.get("__is_structured_resource_element_compatible"));
     ConstraintExpr = constructStructuredBufferConstraintExpr(S, DeclLoc, T);
+    break;
+  case HLSLBufferType::Constant:
+    DeclName = DeclarationName(
+        &Context.Idents.get("__is_constant_buffer_element_compatible"));
+    ConstraintExpr = constructConstantBufferConstraintExpr(S, DeclLoc, T);
+    break;
   }
 
   // Create a ConceptDecl
@@ -468,9 +500,22 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
   ASTContext &AST = SemaPtr->getASTContext();
   CXXRecordDecl *Decl;
   ConceptDecl *TypedBufferConcept = constructBufferConceptDecl(
-      *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ true);
+      *SemaPtr, HLSLNamespace, HLSLBufferType::Typed);
   ConceptDecl *StructuredBufferConcept = constructBufferConceptDecl(
-      *SemaPtr, HLSLNamespace, /*isTypedBuffer*/ false);
+      *SemaPtr, HLSLNamespace, HLSLBufferType::Structured);
+  ConceptDecl *ConstantBufferConcept = constructBufferConceptDecl(
+      *SemaPtr, HLSLNamespace, HLSLBufferType::Constant);
+
+  Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "ConstantBuffer")
+             .addSimpleTemplateParams({"element_type"}, ConstantBufferConcept)
+             .finalizeForwardDeclaration();
+
+  onCompletion(Decl, [this](CXXRecordDecl *Decl) {
+    setupBufferType(Decl, *SemaPtr, ResourceClass::CBuffer, /*IsROV=*/false,
+                    /*RawBuffer=*/false, /*HasCounter=*/false)
+        .addConversionToType()
+        .completeDefinition();
+  });
 
   Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "Buffer")
              .addSimpleTemplateParams({"element_type"}, TypedBufferConcept)
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index a4504410cae28..011a0ab8b1833 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1239,6 +1239,58 @@ static ExprResult LookupMemberExpr(Sema &S, LookupResult &R,
 
   QualType BaseType = BaseExpr.get()->getType();
 
+  // TODO: This code should be improved. It should probably be moved to go with
+  // the other HLSL specific code in this function. HLSL: Intercept member
+  // accesses on ConstantBuffer<T>.
+  if (S.getLangOpts().HLSL) {
+    if (auto *RD = BaseType->getAsCXXRecordDecl()) {
+      if (RD->getName() == "ConstantBuffer") {
+        if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) {
+          if (CTSD->getTemplateArgs().size() > 0) {
+            QualType InnerType = CTSD->getTemplateArgs()[0].getAsType();
+            // Ensure we have the canonical type and strip any references just
+            // in case
+            QualType CanonType =
+                InnerType.getCanonicalType().getNonReferenceType();
+            QualType AddrSpaceType =
+                S.Context.getCanonicalType(S.Context.getAddrSpaceQualType(
+                    CanonType, LangAS::hlsl_constant));
+            QualType ReturnTy = S.Context.getCanonicalType(
+                S.Context.getLValueReferenceType(AddrSpaceType));
+
+            DeclarationName ConvName =
+                S.Context.DeclarationNames.getCXXConversionFunctionName(
+                    CanQualType::CreateUnsafe(ReturnTy));
+            LookupResult ConvR(S, ConvName, OpLoc, Sema::LookupOrdinaryName);
+            if (S.LookupQualifiedName(ConvR, RD)) {
+              CXXConversionDecl *ConvDecl = nullptr;
+              NamedDecl *FoundDecl = nullptr;
+              for (NamedDecl *D : ConvR) {
+                if (auto *CD =
+                        dyn_cast<CXXConversionDecl>(D->getUnderlyingDecl())) {
+                  ConvDecl = CD;
+                  FoundDecl = D;
+                  break;
+                }
+              }
+              if (ConvDecl) {
+                ExprResult ConvCall = S.BuildCXXMemberCallExpr(
+                    BaseExpr.get(), FoundDecl, ConvDecl,
+                    /*HadMultipleCandidates=*/false);
+                if (!ConvCall.isInvalid()) {
+                  BaseExpr = ConvCall;
+                  return LookupMemberExpr(S, R, BaseExpr, IsArrow, OpLoc, SS,
+                                          ObjCImpDecl, HasTemplateArgs,
+                                          TemplateKWLoc);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
   DeclarationName MemberName = R.getLookupName();
   SourceLocation MemberLoc = R.getNameLoc();
 
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index a7d914f08e828..dd7095a6e6fa0 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -77,6 +77,19 @@ static RegisterType getRegisterType(const HLSLAttributedResourceType *ResTy) {
   return getRegisterType(ResTy->getAttrs().ResourceClass);
 }
 
+static LangAS getLangASFromResourceClass(ResourceClass RC) {
+  switch (RC) {
+  case ResourceClass::SRV:
+  case ResourceClass::UAV:
+    return LangAS::hlsl_device;
+  case ResourceClass::CBuffer:
+    return LangAS::hlsl_constant;
+  case ResourceClass::Sampler:
+    return LangAS::hlsl_device;
+  }
+  llvm_unreachable("unexpected ResourceClass value");
+}
+
 // Converts the first letter of string Slot to RegisterType.
 // Returns false if the letter does not correspond to a valid register type.
 static bool convertToRegisterType(StringRef Slot, RegisterType *RT) {
@@ -3828,16 +3841,17 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
     break;
   }
   case Builtin::BI__builtin_hlsl_resource_getpointer: {
-    if (SemaRef.checkArgCount(TheCall, 2) ||
+    if (SemaRef.checkArgCountRange(TheCall, 1, 2) ||
         CheckResourceHandle(&SemaRef, TheCall, 0) ||
-        CheckIndexType(&SemaRef, TheCall, 1))
+        (TheCall->getNumArgs() == 2 && CheckIndexType(&SemaRef, TheCall, 1)))
       return true;
 
     auto *ResourceTy =
         TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
     QualType ContainedTy = ResourceTy->getContainedType();
-    auto ReturnType =
-        SemaRef.Context.getAddrSpaceQualType(ContainedTy, LangAS::hlsl_device);
+    auto ReturnType = SemaRef.Context.getAddrSpaceQualType(
+        ContainedTy,
+        getLangASFromResourceClass(ResourceTy->getAttrs().ResourceClass));
     ReturnType = SemaRef.Context.getPointerType(ReturnType);
     TheCall->setType(ReturnType);
     TheCall->setValueKind(VK_LValue);
@@ -3861,8 +3875,11 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
           cast<FunctionDecl>(SemaRef.CurContext)->getPointOfInstantiation(),
           diag::err_invalid_use_of_array_type);
 
-    auto ReturnType =
-        SemaRef.Context.getAddrSpaceQualType(ElementTy, LangAS::hlsl_device);
+    auto *ResourceTy =
+        TheCall->getArg(0)->getType()->castAs<HLSLAttributedResourceType>();
+    auto ReturnType = SemaRef.Context.getAddrSpaceQualType(
+        ElementTy,
+        getLangASFromResourceClass(ResourceTy->getAttrs().ResourceClass));
     ReturnType = SemaRef.Context.getPointerType(ReturnType);
     TheCall->setType(ReturnType);
 
@@ -4573,6 +4590,22 @@ static void BuildFlattenedTypeList(QualType BaseTy,
   }
 }
 
+bool SemaHLSL::IsConstantBufferElementCompatible(clang::QualType QT) {
+  if (QT.isNull())
+    return false;
+
+  // Must be a class/struct.
+  const auto *RD = QT->getAsCXXRecordDecl();
+  if (!RD || RD->isUnion())
+    return false;
+
+  // Cannot be a resource type or contain one.
+  if (QT->isHLSLIntangibleType())
+    return false;
+
+  return true;
+}
+
 bool SemaHLSL::IsTypedResourceElementCompatible(clang::QualType QT) {
   // null and array types are not allowed.
   if (QT.isNull() || QT->isArrayType())
@@ -4896,6 +4929,8 @@ static bool hasConstantBufferLayout(QualType QT) {
     return false;
 
   if (const auto *RD = Ty->getAsCXXRecordDecl()) {
+    if (RD->isImplicit())
+      return false;
     for (const auto *FD : RD->fields()) {
       if (hasConstantBufferLayout(FD->getType()))
         return true;
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index a94a59e8add7b..c79b3f7045ca6 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -367,6 +367,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
   case UTT_IsCompound:
   case UTT_IsMemberPointer:
   case UTT_IsTypedResourceElementCompatible:
+  case UTT_IsConstantBufferElementCompatible:
     // Fall-through
 
     // These traits are modeled on type predicates in C++0x [meta.unary.prop]
@@ -1131,6 +1132,14 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
       return false;
 
     return Self.HLSL().IsTypedResourceElementCompatible(T);
+
+  case UTT_IsConstantBufferElementCompatible:
+    assert(Self.getLangOpts().HLSL &&
+           "constant buffer element compatible types are an HLSL-only feature");
+    if (T->isIncompleteType())
+      return false;
+
+    return Self.HLSL().IsConstantBufferElementCompatible(T);
   }
 }
 
diff --git a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
new file mode 100644
index 0000000000000..c60c999623465
--- /dev/null
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
@@ -0,0 +1,156 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump -disable-llvm-passes -finclude-default-header -o - %s | FileCheck %s
+
+// CHECK: ClassTemplateDecl {{.*}} ConstantBuffer
+// CHECK: TemplateTypeParmDecl {{.*}} element_type
+// CHECK: CXXRecordDecl {{.*}} ConstantBuffer definition
+// CHECK: FinalAttr {{.*}} Implicit final
+// CHECK-NEXT: FieldDecl {{.*}} implicit __handle '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+
+// CHECK: CXXConstructorDecl {{.*}} ConstantBuffer<element_type> 'void ()' inline
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: CStyleCastExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' <Dependent>
+// CHECK-NEXT: CallExpr {{.*}} '<dependent type>'
+
+// CHECK: CXXConstructorDecl {{.*}} ConstantBuffer<element_type> 'void (const hlsl::ConstantBuffer<element_type> &)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} other 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: DeclRefExpr {{.*}} 'const hlsl::ConstantBuffer<element_type>' lvalue ParmVar {{.*}} 'other' 'const hlsl::ConstantBuffer<element_type> &'
+
+// CHECK: CXXMethodDecl {{.*}} operator= 'hlsl::ConstantBuffer<element_type> &(const hlsl::ConstantBuffer<element_type> &)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} other 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: CompoundStmt
+// CHECK-NEXT: BinaryOperator {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' '='
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+// CHECK-NEXT: MemberExpr {{.*}} '__hlsl_resource_t
+// CHECK-SAME{LITERAL}: [[hlsl::resource_class(CBuffer)]]
+// CHECK-SAME{LITERAL}: [[hlsl::contained_type(element_type)]]
+// CHECK-SAME: ' lvalue .__handle
+// CHECK-NEXT: DeclRefExpr {{.*}} 'const hlsl::ConstantBuffer<element_type>' lvalue ParmVar {{.*}} 'other' 'const hlsl::ConstantBuffer<element_type> &'
+// CHECK-NEXT: ReturnStmt
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::ConstantBuffer<element_type>' lvalue implicit this
+
+struct S {
+  float a;
+};
+ConstantBuffer<S> cb;
+
+struct Nested {
+  S s;
+  float b;
+};
+ConstantBuffer<Nested> cb_nested;
+
+void takes_s(S s) {}
+void takes_cb(ConstantBuffer<S> c) {}
+void takes_inout_cb(inout ConstantBuffer<S> c) {}
+
+float main() {
+  // CHECK: FunctionDecl {{.*}} main
+  // CHECK: MemberExpr {{.*}} 'hlsl_constant float' lvalue .a
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+  float f1 = cb.a;
+
+  // CHECK: MemberExpr {{.*}} 'hlsl_constant float' lvalue .b
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant Nested' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant Nested &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<Nested>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
+  float f2 = cb_nested.b;
+
+  // CHECK: MemberExpr {{.*}} 'hlsl_constant float' lvalue .a
+  // CHECK-NEXT: MemberExpr {{.*}} 'hlsl_constant S' lvalue .s
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant Nested' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant Nested &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<Nested>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
+  float f3 = cb_nested.s.a;
+
+  // CHECK: CallExpr {{.*}} 'void'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(S)' <FunctionToPointerDecay>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'void (S)' lvalue Function {{.*}} 'takes_s' 'void (S)'
+  // CHECK-NEXT: CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+  takes_s(cb);
+
+  // CHECK: CallExpr {{.*}} 'void'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(ConstantBuffer<S>)' <FunctionToPointerDecay>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'void (ConstantBuffer<S>)' lvalue Function {{.*}} 'takes_cb' 'void (ConstantBuffer<S>)'
+  // CHECK-NEXT: CXXConstructExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' 'void (const hlsl::ConstantBuffer<S> &)'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+  takes_cb(cb);
+
+  // CHECK: CallExpr {{.*}} 'void'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout ConstantBuffer<S>)' <FunctionToPointerDecay>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'void (inout ConstantBuffer<S>)' lvalue Function {{.*}} 'takes_inout_cb' 'void (inout ConstantBuffer<S>)'
+  // CHECK-NEXT: HLSLOutArgExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue inout
+  takes_inout_cb(cb);
+
+  // CHECK: DeclStmt
+  // CHECK-NEXT: VarDecl {{.*}} s 'S' cinit
+  // CHECK-NEXT: CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+  S s = cb;
+
+  // CHECK: CXXOperatorCallExpr {{.*}} 'S' lvalue '='
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'S &(*)(const S &) noexcept' <FunctionToPointerDecay>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'S &(const S &) noexcept' lvalue CXXMethod {{.*}} 'operator=' 'S &(const S &) noexcept'
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} 's' 'S'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl_constant S' lvalue <NoOp>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
+  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
+  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
+  s = cb;
+
+  return f1 + f2 + f3;
+}
diff --git a/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
new file mode 100644
index 0000000000000..5e569298722b8
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
@@ -0,0 +1,92 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.0-compute -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DXIL
+// RUN: %clang_cc1 -finclude-default-header -triple spirv-vulkan-library -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIRV
+
+struct S {
+  float a;
+  int b;
+};
+
+// CHECK-DXIL: %"class.hlsl::ConstantBuffer" = type { target("dx.CBuffer", %S) }
+// CHECK-SPIRV: %"class.hlsl::ConstantBuffer" = type { target("spirv.VulkanBuffer", %S, 2, 0) }
+ConstantBuffer<S> cb;
+
+// CHECK-LABEL: define {{.*}} void @_Z4mainv()
+// CHECK-DXIL: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
+// CHECK-DXIL: [[GEP_A:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(2) [[CB_CONV]], i32 0, i32 0
+// CHECK-DXIL: [[LOAD_A:%.*]] = load float, ptr addrspace(2) [[GEP_A]], align 4
+
+// CHECK-SPIRV: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
+// CHECK-SPIRV: [[GEP_A:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(12) [[CB_CONV]], i32 0, i32 0
+// CHECK-SPIRV: [[LOAD_A:%.*]] = load float, ptr addrspace(12) [[GEP_A]], align 4
+
+// CHECK: store float [[LOAD_A]], ptr %f, align 4
+[numthreads(1,1,1)]
+void main() {
+  float f = cb.a;
+}
+
+struct Nested {
+  S s;
+  float c;
+};
+
+ConstantBuffer<Nested> cb_nested[2];
+
+[numthreads(1,1,1)]
+void foo() {
+  // CHECK-LABEL: define {{.*}} void @_Z3foov()
+  // CHECK-DXIL: [[TMP_CB:%.*]] = alloca %"class.hlsl::ConstantBuffer.0", align 4
+  // CHECK-DXIL: call void @_ZN4hlsl14ConstantBufferI6NestedE27__createFromImplicitBindingEjjijPKc(ptr dead_on_unwind writable sret(%"class.hlsl::ConstantBuffer.0") align 4 [[TMP_CB]], i32 noundef 1, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr noundef @cb_nested.str)
+  // CHECK-DXIL: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI6NestedEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) [[TMP_CB]])
+  // CHECK-DXIL: [[GEP_S:%.*]] = getelementptr inbounds nuw %Nested, ptr addrspace(2) [[CB_CONV]], i32 0, i32 0
+  // CHECK-DXIL: [[GEP_A2:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(2) [[GEP_S]], i32 0, i32 0
+  // CHECK-DXIL: [[LOAD_A2:%.*]] = load float, ptr addrspace(2) [[GEP_A2]], align 4
+
+  // CHECK-SPIRV: [[TMP_CB:%.*]] = alloca %"class.hlsl::ConstantBuffer.0", align 8
+  // CHECK-SPIRV: call void @_ZN4hlsl14ConstantBufferI6NestedE27__createFromImplicitBindingEjjijPKc(ptr dead_on_unwind writable sret(%"class.hlsl::ConstantBuffer.0") align 8 [[TMP_CB]], i32 noundef 1, i32 noundef 0, i32 noundef 2, i32 noundef 1, ptr noundef @cb_nested.str)
+  // CHECK-SPIRV: [[CB_CONV:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI6NestedEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) [[TMP_CB]])
+  // CHECK-SPIRV: [[GEP_S:%.*]] = getelementptr inbounds nuw %Nested, ptr addrspace(12) [[CB_CONV]], i32 0, i32 0
+  // CHECK-SPIRV: [[GEP_A2:%.*]] = getelementptr inbounds nuw %S, ptr addrspace(12) [[GEP_S]], i32 0, i32 0
+  // CHECK-SPIRV: [[LOAD_A2:%.*]] = load float, ptr addrspace(12) [[GEP_A2]], align 4
+
+  // CHECK: store float [[LOAD_A2]], ptr %f2, align 4
+  float f2 = cb_nested[1].s.a;
+}
+
+void takes_s(S s) {}
+void takes_cb(ConstantBuffer<S> c) {}
+
+[numthreads(1,1,1)]
+void test_assignments_and_params() {
+  // CHECK-LABEL: define {{.*}} void @_Z27test_assignments_and_paramsv()
+  
+  // CHECK-DXIL: [[CB_CONV1:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
+  // CHECK-DXIL: [[CB_AS1:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV1]] to ptr
+  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %s, ptr align 1 [[CB_AS1]], i32 8, i1 false)
+  // CHECK-SPIRV: [[CB_CONV1:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
+  // CHECK-SPIRV: [[CB_AS1:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV1]] to ptr
+  // CHECK-SPIRV: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %s, ptr align 1 [[CB_AS1]], i64 8, i1 false)
+  S s = cb;
+
+  // CHECK-DXIL: [[CB_CONV2:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
+  // CHECK-DXIL: [[CB_AS2:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV2]] to ptr
+  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %s, ptr align 1 [[CB_AS2]], i32 8, i1 false)
+  // CHECK-SPIRV: [[CB_CONV2:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
+  // CHECK-SPIRV: [[CB_AS2:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV2]] to ptr
+  // CHECK-SPIRV: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %s, ptr align 1 [[CB_AS2]], i64 8, i1 false)
+  s = cb;
+
+  // CHECK-DXIL: [[CB_CONV3:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
+  // CHECK-DXIL: [[CB_AS3:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV3]] to ptr
+  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %agg.tmp, ptr align 1 [[CB_AS3]], i32 8, i1 false)
+  // CHECK-DXIL: call void @_Z7takes_s1S(ptr noundef byval(%struct.S) align 1 %agg.tmp)
+  // CHECK-SPIRV: [[CB_CONV3:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
+  // CHECK-SPIRV: [[CB_AS3:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV3]] to ptr
+  // CHECK-SPIRV: call {{.*}} void @_Z7takes_s1S(ptr noundef byval(%struct.S) align 1 %agg.tmp)
+  takes_s(cb);
+
+  // CHECK: call void @_ZN4hlsl14ConstantBufferI1SEC1ERKS2_(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %agg.tmp{{[0-9]+}}, ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @_ZL2cb)
+  // CHECK-DXIL: call void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp{{[0-9]+}})
+  // CHECK-SPIRV: call {{.*}} void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp{{[0-9]+}})
+  takes_cb(cb);
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
new file mode 100644
index 0000000000000..24fe649c3358d
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+struct S {
+    float a;
+    
+    float foo() {
+        return a;
+    };
+
+    void bar() {
+        a = 1.0;
+    }
+};
+
+ConstantBuffer<S> CB;
+
+[numthreads(4,1,1)]
+void main() {
+    // Calling non-const member function is allowed for parity with DXC.
+    float tmp = CB.foo();
+
+    // Even if it modifies the buffer, it's allowed in Sema (backended/validations will catch it).
+    CB.bar();
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
new file mode 100644
index 0000000000000..41d20dba28003
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+// expected-no-diagnostics
+
+struct T {
+    int a;
+};
+
+ConstantBuffer<T> c;
+
+RWBuffer<int> b;
+
+template<class Tm>
+void foo(Tm t) {
+    b[0] = t.a;
+}
+
+[numthreads(1,1,1)]
+void main() {
+    T t = c;
+    foo(c);
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
new file mode 100644
index 0000000000000..bb222b8171234
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
@@ -0,0 +1,62 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
+
+struct S {
+  float a;
+  int b;
+};
+
+struct Empty {};
+
+struct ContainsResource {
+  Texture2D tex;
+};
+
+union U {
+  float a;
+  int b;
+};
+
+// Valid
+ConstantBuffer<S> cb;
+ConstantBuffer<Empty> cb_empty;
+
+// Invalid: non-struct/class
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float> cb_float;
+// expected-note@* {{because 'float' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(float)' evaluated to false}}
+
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float4> cb_float4;
+// expected-note@* {{because 'float4' (aka 'vector<float, 4>') does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(vector<float, 4>)' evaluated to false}}
+
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<float[4]> cb_array;
+// expected-note@* {{because 'float[4]' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(float[4])' evaluated to false}}
+
+// Invalid: contains resource
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<ContainsResource> cb_res;
+// expected-note@* {{because 'ContainsResource' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(ContainsResource)' evaluated to false}}
+
+// Invalid: intangible type
+// expected-error at +1 {{use of class template 'Texture2D' requires template arguments}}
+ConstantBuffer<Texture2D> cb_tex;
+// expected-note@* {{template declaration from hidden source}}
+
+// Invalid: union
+// expected-error at +1 {{constraints not satisfied for class template 'ConstantBuffer'}}
+ConstantBuffer<U> cb_union;
+// expected-note@* {{because 'U' does not satisfy '__is_constant_buffer_element_compatible'}}
+// expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(U)' evaluated to false}}
+
+// expected-error at +1 {{no viable overloaded '='}}
+void takes_inout_s(inout S s) {}
+// expected-note@*:* {{candidate function not viable: no known conversion from 'S' to 'const hlsl::ConstantBuffer<S>' for 1st argument}}
+
+void foo() {
+  takes_inout_s(cb); // Fails because of inout writeback
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
index 20de0773a1742..c0b2a9338114a 100644
--- a/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/resource_getpointer-errors.hlsl
@@ -5,10 +5,10 @@ using handle_t = __hlsl_resource_t
     [[hlsl::resource_class(UAV)]] [[hlsl::contained_type(int)]];
 
 void test_args(unsigned int x) {
-  // expected-error at +1 {{too few arguments to function call, expected 2, have 1}}
+  // expected-error at +1 {{used type 'unsigned int' where __hlsl_resource_t is required}}
   __builtin_hlsl_resource_getpointer(x);
 
-  // expected-error at +1 {{too many arguments to function call, expected 2, have 3}}
+  // expected-error at +1 {{too many arguments to function call, expected at most 2, have 3}}
   __builtin_hlsl_resource_getpointer(x, x, x);
 
   // expected-error at +1 {{used type 'unsigned int' where __hlsl_resource_t is required}}
@@ -16,6 +16,9 @@ void test_args(unsigned int x) {
 
   handle_t res;
 
+  // no error
+  __builtin_hlsl_resource_getpointer(res);
+
   // expected-error at +1 {{used type 'const char *' where integer is required}}
   __builtin_hlsl_resource_getpointer(res, "1");
 
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index f37180ce9084a..5168d7a52a044 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -40,6 +40,10 @@ def int_dx_resource_getpointer
     : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
                             [IntrReadMem, IntrInaccessibleMemOnly]>;
 
+def int_dx_resource_getbasepointer
+    : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
+                            [IntrConvergent, IntrNoMem]>;
+
 def int_dx_resource_nonuniformindex
     : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty], [IntrNoMem]>;
 
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 44e31a1410523..6157947ad2318 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -318,6 +318,10 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
       : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty, llvm_any_ty],
                               [IntrNoMem]>;
 
+  def int_spv_resource_getbasepointer
+      : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty],
+                              [IntrNoMem]>;
+
   def int_spv_pushconstant_getpointer
       : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [llvm_any_ty], [IntrNoMem]>;
 
diff --git a/llvm/lib/Target/DirectX/DXILMemIntrinsics.cpp b/llvm/lib/Target/DirectX/DXILMemIntrinsics.cpp
index fa8b7dae6c76a..d477793464201 100644
--- a/llvm/lib/Target/DirectX/DXILMemIntrinsics.cpp
+++ b/llvm/lib/Target/DirectX/DXILMemIntrinsics.cpp
@@ -69,7 +69,8 @@ static Type *getPointeeType(Value *Ptr, const DataLayout &DL) {
     return AI->getAllocatedType();
 
   if (auto *II = dyn_cast<IntrinsicInst>(Ptr)) {
-    if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
+    if (II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer ||
+        II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
       Type *Ty = cast<dxil::AnyResourceExtType>(II->getArgOperand(0)->getType())
                      ->getResourceType();
       assert(Ty && "getpointer used on untyped resource");
diff --git a/llvm/lib/Target/DirectX/DXILOpLowering.cpp b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
index b4d95dc66d3cc..cf19005895386 100644
--- a/llvm/lib/Target/DirectX/DXILOpLowering.cpp
+++ b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
@@ -1047,6 +1047,7 @@ class OpLowerer {
       case Intrinsic::dx_resource_handlefrombinding:
         HasErrors |= lowerHandleFromBinding(F);
         break;
+      case Intrinsic::dx_resource_getbasepointer:
       case Intrinsic::dx_resource_getpointer:
         HasErrors |= lowerGetPointer(F);
         break;
diff --git a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
index 132f1aa8d0aa4..cacd46e01982c 100644
--- a/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
+++ b/llvm/lib/Target/DirectX/DXILResourceAccess.cpp
@@ -57,7 +57,8 @@ static Value *traverseGEPOffsets(const DataLayout &DL, IRBuilder<> &Builder,
 
   while (Ptr) {
     if (auto *II = dyn_cast<IntrinsicInst>(Ptr)) {
-      assert(II->getIntrinsicID() == Intrinsic::dx_resource_getpointer &&
+      assert((II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer ||
+              II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) &&
              "Resource access through unexpected intrinsic");
       return Offset ? Offset : ConstantInt::get(Builder.getInt32Ty(), 0);
     }
@@ -574,7 +575,8 @@ static SmallVector<IntrinsicInst *> collectUsedHandles(Value *Ptr) {
     else if (auto *II = dyn_cast<IntrinsicInst>(X)) {
       Intrinsic::ID IID = II->getIntrinsicID();
 
-      if (IID == Intrinsic::dx_resource_getpointer)
+      if (IID == Intrinsic::dx_resource_getbasepointer ||
+          IID == Intrinsic::dx_resource_getpointer)
         Worklist.push_back(II->getArgOperand(/*Handle=*/0));
 
       if (llvm::is_contained(HandleIntrins, IID))
@@ -625,14 +627,16 @@ getAccessIndices(Instruction *I, SmallSetVector<Instruction *, 16> &DeadInsts) {
       return {nullptr, II->getArgOperand(/*Index=*/3)};
     }
 
-    if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
+    if (II->getIntrinsicID() == Intrinsic::dx_resource_getbasepointer ||
+        II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
       auto *V = dyn_cast<Instruction>(II->getArgOperand(/*Handle=*/0));
       auto AccessIdx = getAccessIndices(V, DeadInsts);
       assert(!AccessIdx.hasGetPtrIdx() &&
              "Encountered multiple dx.resource.getpointers in ptr chain?");
-      AccessIdx.GetPtrIdx = II->getArgOperand(1);
-
-      DeadInsts.insert(II);
+      if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer)
+        AccessIdx.GetPtrIdx = II->getArgOperand(1);
+      else
+        AccessIdx.GetPtrIdx = nullptr;
       return AccessIdx;
     }
   }
@@ -696,7 +700,7 @@ static void
 replaceHandleWithIndices(Instruction *Ptr, IntrinsicInst *OldHandle,
                          SmallSetVector<Instruction *, 16> &DeadInsts) {
   auto AccessIdx = getAccessIndices(Ptr, DeadInsts);
-  assert(AccessIdx.hasGetPtrIdx() && AccessIdx.hasHandleIdx() &&
+  assert(AccessIdx.hasHandleIdx() &&
          "Couldn't retrieve indices. This is guaranteed by getAccessIndices");
 
   IRBuilder<> Builder(Ptr);
@@ -704,9 +708,15 @@ replaceHandleWithIndices(Instruction *Ptr, IntrinsicInst *OldHandle,
   Handle->setArgOperand(/*Index=*/3, AccessIdx.HandleIdx);
   Builder.Insert(Handle);
 
-  auto *GetPtr =
-      Builder.CreateIntrinsic(Ptr->getType(), Intrinsic::dx_resource_getpointer,
-                              {Handle, AccessIdx.GetPtrIdx});
+  Intrinsic::ID GetPtrIID = AccessIdx.hasGetPtrIdx()
+                                ? Intrinsic::dx_resource_getpointer
+                                : Intrinsic::dx_resource_getbasepointer;
+  SmallVector<Value *, 2> Args;
+  Args.push_back(Handle);
+  if (AccessIdx.hasGetPtrIdx())
+    Args.push_back(AccessIdx.GetPtrIdx);
+
+  auto *GetPtr = Builder.CreateIntrinsic(Ptr->getType(), GetPtrIID, Args);
 
   Ptr->replaceAllUsesWith(GetPtr);
   DeadInsts.insert(Ptr);
@@ -796,11 +806,14 @@ static bool transformResourcePointers(Function &F, DXILResourceTypeMap &DRTM) {
   SmallVector<std::pair<IntrinsicInst *, dxil::ResourceTypeInfo>> Resources;
   for (BasicBlock &BB : make_early_inc_range(F))
     for (Instruction &I : BB)
-      if (auto *II = dyn_cast<IntrinsicInst>(&I))
-        if (II->getIntrinsicID() == Intrinsic::dx_resource_getpointer) {
+      if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
+        Intrinsic::ID IID = II->getIntrinsicID();
+        if (IID == Intrinsic::dx_resource_getbasepointer ||
+            IID == Intrinsic::dx_resource_getpointer) {
           auto *HandleTy = cast<TargetExtType>(II->getArgOperand(0)->getType());
           Resources.emplace_back(II, DRTM[HandleTy]);
         }
+      }
 
   for (auto &[II, RI] : Resources)
     replaceAccess(II, RI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index 86aa83f1b8ded..2014f3bbf0b9d 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -411,6 +411,7 @@ bool isConvergenceIntrinsic(const Instruction *I) {
 bool expectIgnoredInIRTranslation(const Instruction *I) {
   return match(I, m_AnyIntrinsic<Intrinsic::invariant_start,
                                  Intrinsic::spv_resource_handlefrombinding,
+                                 Intrinsic::spv_resource_getbasepointer,
                                  Intrinsic::spv_resource_getpointer>());
 }
 
@@ -1014,7 +1015,8 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
     // TODO: maybe improve performance by caching demangled names
 
     auto *II = dyn_cast<IntrinsicInst>(I);
-    if (II && II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+    if (II && (II->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+               II->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
       auto *HandleType = cast<TargetExtType>(II->getOperand(0)->getType());
       if (HandleType->getTargetExtName() == "spirv.Image" ||
           HandleType->getTargetExtName() == "spirv.SignedImage") {
@@ -1026,12 +1028,15 @@ Type *SPIRVEmitIntrinsics::deduceElementTypeHelper(
       } else if (HandleType->getTargetExtName() == "spirv.VulkanBuffer") {
         // This call is supposed to index into an array
         Ty = HandleType->getTypeParameter(0);
-        if (Ty->isArrayTy())
-          Ty = Ty->getArrayElementType();
-        else {
-          assert(Ty && Ty->isStructTy());
-          uint32_t Index = cast<ConstantInt>(II->getOperand(1))->getZExtValue();
-          Ty = cast<StructType>(Ty)->getElementType(Index);
+        if (II->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+          if (Ty->isArrayTy())
+            Ty = Ty->getArrayElementType();
+          else {
+            assert(Ty && Ty->isStructTy());
+            uint32_t Index =
+                cast<ConstantInt>(II->getOperand(1))->getZExtValue();
+            Ty = cast<StructType>(Ty)->getElementType(Index);
+          }
         }
         Ty = reconstitutePeeledArrayType(Ty);
       } else {
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 28988129fa917..664f2470bdced 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -700,6 +700,7 @@ static bool intrinsicHasSideEffects(Intrinsic::ID ID) {
   case Intrinsic::spv_radians:
   case Intrinsic::spv_reflect:
   case Intrinsic::spv_refract:
+  case Intrinsic::spv_resource_getbasepointer:
   case Intrinsic::spv_resource_getpointer:
   case Intrinsic::spv_resource_handlefrombinding:
   case Intrinsic::spv_resource_handlefromimplicitbinding:
@@ -1887,7 +1888,9 @@ bool SPIRVInstructionSelector::selectLoad(Register ResVReg,
   auto *PtrDef = getVRegDef(*MRI, Ptr);
   auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
   if (IntPtrDef &&
-      IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+      (IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+       IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
+
     Register HandleReg = IntPtrDef->getOperand(2).getReg();
     SPIRVTypeInst HandleType = GR.getSPIRVTypeForVReg(HandleReg);
     if (HandleType->getOpcode() == SPIRV::OpTypeImage) {
@@ -1979,7 +1982,9 @@ bool SPIRVInstructionSelector::selectStore(MachineInstr &I) const {
   auto *PtrDef = getVRegDef(*MRI, Ptr);
   auto *IntPtrDef = dyn_cast<GIntrinsic>(PtrDef);
   if (IntPtrDef &&
-      IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer) {
+      (IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getbasepointer ||
+       IntPtrDef->getIntrinsicID() == Intrinsic::spv_resource_getpointer)) {
+
     Register HandleReg = IntPtrDef->getOperand(2).getReg();
     Register NewHandleReg =
         MRI->createVirtualRegister(MRI->getRegClass(HandleReg));
@@ -5096,6 +5101,7 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
   case Intrinsic::spv_resource_gather:
   case Intrinsic::spv_resource_gather_cmp:
     return selectGatherIntrinsic(ResVReg, ResType, I);
+  case Intrinsic::spv_resource_getbasepointer:
   case Intrinsic::spv_resource_getpointer: {
     return selectResourceGetPointer(ResVReg, ResType, I);
   }
@@ -5892,16 +5898,20 @@ bool SPIRVInstructionSelector::selectResourceGetPointer(Register &ResVReg,
   assert(ResType->getOpcode() == SPIRV::OpTypePointer);
   MachineIRBuilder MIRBuilder(I);
 
-  Register IndexReg = I.getOperand(3).getReg();
   Register ZeroReg =
       buildZerosVal(GR.getOrCreateSPIRVIntegerType(32, I, TII), I);
-  BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpAccessChain))
-      .addDef(ResVReg)
-      .addUse(GR.getSPIRVTypeID(ResType))
-      .addUse(ResourcePtr)
-      .addUse(ZeroReg)
-      .addUse(IndexReg)
-      .constrainAllUses(TII, TRI, RBI);
+  auto MIB =
+      BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpAccessChain))
+          .addDef(ResVReg)
+          .addUse(GR.getSPIRVTypeID(ResType))
+          .addUse(ResourcePtr)
+          .addUse(ZeroReg);
+
+  if (I.getNumExplicitOperands() > 3) {
+    Register IndexReg = I.getOperand(3).getReg();
+    MIB.addUse(IndexReg);
+  }
+  MIB.constrainAllUses(TII, TRI, RBI);
   return true;
 }
 

>From 1c3019d569fa1c58ea3856092774102c93a9e91c Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Tue, 28 Apr 2026 11:19:28 -0400
Subject: [PATCH 2/3] Remove address space change

---
 clang/lib/AST/Type.cpp                        |  1 -
 clang/lib/CodeGen/CGExprAgg.cpp               | 12 ++++--
 clang/test/AST/HLSL/ConstantBuffers-AST.hlsl  | 36 ------------------
 .../CodeGenHLSL/builtins/ConstantBuffer.hlsl  | 37 +++----------------
 .../BuiltIns/ConstantBuffer-member-funcs.hlsl |  6 +--
 .../BuiltIns/ConstantBuffer-template.hlsl     |  6 +--
 .../SemaHLSL/BuiltIns/ConstantBuffers.hlsl    |  5 +--
 7 files changed, 20 insertions(+), 83 deletions(-)

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 81a4ed2f3c162..6c295c1a9c409 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -100,7 +100,6 @@ bool Qualifiers::isTargetAddressSpaceSupersetOf(LangAS A, LangAS B,
          // address spaces to default to work around this problem.
          (A == LangAS::Default && B == LangAS::hlsl_private) ||
          (A == LangAS::Default && B == LangAS::hlsl_device) ||
-         (A == LangAS::Default && B == LangAS::hlsl_constant) ||
          (A == LangAS::Default && B == LangAS::hlsl_input) ||
          (A == LangAS::Default && B == LangAS::hlsl_output) ||
          (A == LangAS::Default && B == LangAS::hlsl_push_constant) ||
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index a4282c4f51199..126b4577847f6 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -2286,9 +2286,15 @@ 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;
+  if (getLangOpts().HLSL) {
+    unsigned ConstantAS =
+        getContext().getTargetAddressSpace(LangAS::hlsl_constant);
+    if (Ty.getAddressSpace() == LangAS::hlsl_constant ||
+        SrcPtr.getAddressSpace() == ConstantAS) {
+      if (CGM.getHLSLRuntime().emitBufferCopy(*this, DestPtr, SrcPtr, Ty))
+        return;
+    }
+  }
 
   // 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/test/AST/HLSL/ConstantBuffers-AST.hlsl b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
index c60c999623465..49326363fd879 100644
--- a/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/ConstantBuffers-AST.hlsl
@@ -102,18 +102,6 @@ float main() {
   // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>' lvalue Var {{.*}} 'cb_nested' 'ConstantBuffer<Nested>':'hlsl::ConstantBuffer<Nested>'
   float f3 = cb_nested.s.a;
 
-  // CHECK: CallExpr {{.*}} 'void'
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(S)' <FunctionToPointerDecay>
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'void (S)' lvalue Function {{.*}} 'takes_s' 'void (S)'
-  // CHECK-NEXT: CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
-  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
-  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
-  takes_s(cb);
-
   // CHECK: CallExpr {{.*}} 'void'
   // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(ConstantBuffer<S>)' <FunctionToPointerDecay>
   // CHECK-NEXT: DeclRefExpr {{.*}} 'void (ConstantBuffer<S>)' lvalue Function {{.*}} 'takes_cb' 'void (ConstantBuffer<S>)'
@@ -128,29 +116,5 @@ float main() {
   // CHECK-NEXT: HLSLOutArgExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue inout
   takes_inout_cb(cb);
 
-  // CHECK: DeclStmt
-  // CHECK-NEXT: VarDecl {{.*}} s 'S' cinit
-  // CHECK-NEXT: CXXConstructExpr {{.*}} 'S' 'void (const S &) noexcept'
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
-  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
-  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
-  S s = cb;
-
-  // CHECK: CXXOperatorCallExpr {{.*}} 'S' lvalue '='
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'S &(*)(const S &) noexcept' <FunctionToPointerDecay>
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'S &(const S &) noexcept' lvalue CXXMethod {{.*}} 'operator=' 'S &(const S &) noexcept'
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'S' lvalue Var {{.*}} 's' 'S'
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const S' lvalue <AddressSpaceConversion>
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl_constant S' lvalue <NoOp>
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'hlsl_constant S' lvalue <UserDefinedConversion>
-  // CHECK-NEXT: CXXMemberCallExpr {{.*}} 'hlsl_constant S' lvalue
-  // CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator hlsl_constant S &
-  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'const hlsl::ConstantBuffer<S>' lvalue <NoOp>
-  // CHECK-NEXT: DeclRefExpr {{.*}} 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>' lvalue Var {{.*}} 'cb' 'ConstantBuffer<S>':'hlsl::ConstantBuffer<S>'
-  s = cb;
-
   return f1 + f2 + f3;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
index 5e569298722b8..28d91ba17fb33 100644
--- a/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
+++ b/clang/test/CodeGenHLSL/builtins/ConstantBuffer.hlsl
@@ -53,40 +53,13 @@ void foo() {
   float f2 = cb_nested[1].s.a;
 }
 
-void takes_s(S s) {}
 void takes_cb(ConstantBuffer<S> c) {}
 
 [numthreads(1,1,1)]
-void test_assignments_and_params() {
-  // CHECK-LABEL: define {{.*}} void @_Z27test_assignments_and_paramsv()
-  
-  // CHECK-DXIL: [[CB_CONV1:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
-  // CHECK-DXIL: [[CB_AS1:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV1]] to ptr
-  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %s, ptr align 1 [[CB_AS1]], i32 8, i1 false)
-  // CHECK-SPIRV: [[CB_CONV1:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
-  // CHECK-SPIRV: [[CB_AS1:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV1]] to ptr
-  // CHECK-SPIRV: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %s, ptr align 1 [[CB_AS1]], i64 8, i1 false)
-  S s = cb;
-
-  // CHECK-DXIL: [[CB_CONV2:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
-  // CHECK-DXIL: [[CB_AS2:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV2]] to ptr
-  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %s, ptr align 1 [[CB_AS2]], i32 8, i1 false)
-  // CHECK-SPIRV: [[CB_CONV2:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
-  // CHECK-SPIRV: [[CB_AS2:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV2]] to ptr
-  // CHECK-SPIRV: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %s, ptr align 1 [[CB_AS2]], i64 8, i1 false)
-  s = cb;
-
-  // CHECK-DXIL: [[CB_CONV3:%.*]] = call noundef {{.*}} ptr addrspace(2) @_ZNK4hlsl14ConstantBufferI1SEcvRU3AS2S1_Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL2cb)
-  // CHECK-DXIL: [[CB_AS3:%.*]] = addrspacecast ptr addrspace(2) [[CB_CONV3]] to ptr
-  // CHECK-DXIL: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %agg.tmp, ptr align 1 [[CB_AS3]], i32 8, i1 false)
-  // CHECK-DXIL: call void @_Z7takes_s1S(ptr noundef byval(%struct.S) align 1 %agg.tmp)
-  // CHECK-SPIRV: [[CB_CONV3:%.*]] = call noundef {{.*}} ptr addrspace(12) @_ZNK4hlsl14ConstantBufferI1SEcvRU4AS12S1_Ev(ptr noundef nonnull align 8 dereferenceable(8) @_ZL2cb)
-  // CHECK-SPIRV: [[CB_AS3:%.*]] = addrspacecast ptr addrspace(12) [[CB_CONV3]] to ptr
-  // CHECK-SPIRV: call {{.*}} void @_Z7takes_s1S(ptr noundef byval(%struct.S) align 1 %agg.tmp)
-  takes_s(cb);
-
-  // CHECK: call void @_ZN4hlsl14ConstantBufferI1SEC1ERKS2_(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %agg.tmp{{[0-9]+}}, ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @_ZL2cb)
-  // CHECK-DXIL: call void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp{{[0-9]+}})
-  // CHECK-SPIRV: call {{.*}} void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp{{[0-9]+}})
+void test_params() {
+  // CHECK-LABEL: define {{.*}} void @_Z11test_paramsv()
+  // CHECK: call void @_ZN4hlsl14ConstantBufferI1SEC1ERKS2_(ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) %agg.tmp, ptr noundef nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) @_ZL2cb)
+  // CHECK-DXIL: call void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp)
+  // CHECK-SPIRV: call {{.*}} void @_Z8takes_cbN4hlsl14ConstantBufferI1SEE(ptr noundef dead_on_return %agg.tmp)
   takes_cb(cb);
 }
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
index 24fe649c3358d..050eeb2f334ae 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-member-funcs.hlsl
@@ -1,7 +1,5 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
 
-// expected-no-diagnostics
-
 struct S {
     float a;
     
@@ -19,8 +17,8 @@ ConstantBuffer<S> CB;
 [numthreads(4,1,1)]
 void main() {
     // Calling non-const member function is allowed for parity with DXC.
-    float tmp = CB.foo();
+    float tmp = CB.foo(); // expected-error {{cannot initialize object parameter of type 'S' with an expression of type 'hlsl_constant S'}}
 
     // Even if it modifies the buffer, it's allowed in Sema (backended/validations will catch it).
-    CB.bar();
+    CB.bar(); // expected-error {{cannot initialize object parameter of type 'S' with an expression of type 'hlsl_constant S'}}
 }
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
index 41d20dba28003..c6735ddd5b66d 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffer-template.hlsl
@@ -1,8 +1,6 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -finclude-default-header -fsyntax-only -verify %s
 
-// expected-no-diagnostics
-
-struct T {
+struct T { // expected-note 3 {{candidate constructor}}
     int a;
 };
 
@@ -17,6 +15,6 @@ void foo(Tm t) {
 
 [numthreads(1,1,1)]
 void main() {
-    T t = c;
+    T t = c; // expected-error {{no viable constructor copying variable of type 'hlsl_constant T'}}
     foo(c);
 }
diff --git a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
index bb222b8171234..646a5e86a287a 100644
--- a/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/ConstantBuffers.hlsl
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -finclude-default-header -fsyntax-only -verify %s
 
-struct S {
+struct S { // expected-note 3 {{candidate constructor}}
   float a;
   int b;
 };
@@ -53,10 +53,9 @@ ConstantBuffer<U> cb_union;
 // expected-note@* {{because 'U' does not satisfy '__is_constant_buffer_element_compatible'}}
 // expected-note@* {{because '__builtin_hlsl_is_constant_buffer_element_compatible(U)' evaluated to false}}
 
-// expected-error at +1 {{no viable overloaded '='}}
 void takes_inout_s(inout S s) {}
-// expected-note@*:* {{candidate function not viable: no known conversion from 'S' to 'const hlsl::ConstantBuffer<S>' for 1st argument}}
 
 void foo() {
+  // expected-error at +1 {{no viable constructor copying parameter of type 'hlsl_constant S'}}
   takes_inout_s(cb); // Fails because of inout writeback
 }

>From 61fa3a956665d4fcd84db48a2746ef0cdbd8ed62 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Tue, 28 Apr 2026 11:33:11 -0400
Subject: [PATCH 3/3] Remove useless changes.

---
 clang/lib/CodeGen/CGExprAgg.cpp | 12 +++---------
 clang/lib/Sema/SemaHLSL.cpp     |  2 --
 2 files changed, 3 insertions(+), 11 deletions(-)

diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 126b4577847f6..a4282c4f51199 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -2286,15 +2286,9 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
     }
   }
 
-  if (getLangOpts().HLSL) {
-    unsigned ConstantAS =
-        getContext().getTargetAddressSpace(LangAS::hlsl_constant);
-    if (Ty.getAddressSpace() == LangAS::hlsl_constant ||
-        SrcPtr.getAddressSpace() == ConstantAS) {
-      if (CGM.getHLSLRuntime().emitBufferCopy(*this, DestPtr, SrcPtr, Ty))
-        return;
-    }
-  }
+  if (getLangOpts().HLSL && Ty.getAddressSpace() == LangAS::hlsl_constant)
+    if (CGM.getHLSLRuntime().emitBufferCopy(*this, DestPtr, SrcPtr, Ty))
+      return;
 
   // 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/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index dd7095a6e6fa0..abe2478ea4775 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4929,8 +4929,6 @@ static bool hasConstantBufferLayout(QualType QT) {
     return false;
 
   if (const auto *RD = Ty->getAsCXXRecordDecl()) {
-    if (RD->isImplicit())
-      return false;
     for (const auto *FD : RD->fields()) {
       if (hasConstantBufferLayout(FD->getType()))
         return true;



More information about the cfe-commits mailing list