Skip to content

[HLSL] Add resource constructor with implicit binding for global resources #138976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

hekota
Copy link
Member

@hekota hekota commented May 7, 2025

Adds constructor for resources with implicit binding and applies it to all resources without binding at the global scope.
Adds Clang builtin function __builtin_hlsl_resource_handlefromimplicitbinding that gets translated to llvm.dx|spv.resource.handlefromimplicitbinding intrinsic calls. Specific bindings are assigned in DXILResourceImplicitBinding pass.

Design proposals:
https://github.com/llvm/wg-hlsl/blob/main/proposals/0024-implicit-resource-binding.md
https://github.com/llvm/wg-hlsl/blob/main/proposals/0025-resource-constructors.md

One change from the proposals is that the order_id parameter is added onto the constructor. Originally it was supposed to be generated in codegen when the llvm.dx|spv.resource.handlefromimplicitbinding call is emitted, but that is not possible because the call is inside a constructor, and the constructor body is generated once per resource type and not resource instance. So the only way to inject instance-based data like order_id into the llvm.dx|spv.resource.handlefromimplicitbinding call is that it must come in via the constructor argument.

Closes #136784

hekota added 14 commits April 24, 2025 13:27
DXILResourceBindingAnalysis analyses all explicit resource bindings in the module
and puts together lists of used virtual register spaces and available virtual
register slot ranges for each binding type. It also stores additional information
found during the analysis such as whether the module uses implicit bindings or if
any of the bindings overlap.

This information will be used in DXILResourceImplicitBindings pass to
assign register slots to resources with implicit bindings, and in a
post-optimization validation pass that will raise diagnostic about
overlapping bindings.

Part 1/2 of llvm#136786
This pass takes advantage of the DXILResourceBinding analysis and assigns
register slots to resources that do not have explicit binding.

Part 2/2 of llvm#136786

Closes llvm#136786
…urces

Adds constructor for resources with implicit binding and applies it to
all resources without binding at the global scope. Adds Clang buildin
function __builtin_hlsl_resource_handlefromimplicitbinding that it translated
to llvm.dx|spv.resource.handlefromimplicitbinding calls. Specific bindings are
assigned in DXILResourceImplicitBinding pass.

Depends on llvm#138043

Closes llvm#136784
@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 llvm:ir labels May 7, 2025
@llvmbot
Copy link
Member

llvmbot commented May 7, 2025

@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-clang-codegen

Author: Helena Kotas (hekota)

Changes

Adds constructor for resources with implicit binding and applies it to all resources without binding at the global scope.
Adds Clang builtin function __builtin_hlsl_resource_handlefromimplicitbinding that gets translated to llvm.dx|spv.resource.handlefromimplicitbinding intrinsic calls. Specific bindings are assigned in DXILResourceImplicitBinding pass.

Design proposals:
https://github.com/llvm/wg-hlsl/blob/main/proposals/0024-implicit-resource-binding.md
https://github.com/llvm/wg-hlsl/blob/main/proposals/0025-resource-constructors.md

One change from the proposals is that the order_id parameter is added onto the constructor. Originally it was supposed to be generated in codegen when the llvm.dx|spv.resource.handlefromimplicitbinding call is emitted, but that is not possible because the call is inside a constructor, and the constructor body is generated once per resource type and not resource instance. So the only way to inject instance-based data like order_id into the llvm.dx|spv.resource.handlefromimplicitbinding call is that it must come in via the constructor argument.

Depends on #138043

Closes #136784


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

18 Files Affected:

  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+7)
  • (modified) clang/lib/CodeGen/CGHLSLBuiltins.cpp (+15)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+2)
  • (modified) clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp (+20)
  • (modified) clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h (+2-1)
  • (modified) clang/lib/Sema/HLSLExternalSemaSource.cpp (+2-1)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+52-20)
  • (modified) clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl (+22)
  • (modified) clang/test/AST/HLSL/StructuredBuffers-AST.hlsl (+22)
  • (modified) clang/test/AST/HLSL/TypedBuffers-AST.hlsl (+22)
  • (modified) clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl (+1-1)
  • (modified) clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl (+51-4)
  • (modified) clang/test/CodeGenHLSL/builtins/RWBuffer-constructor.hlsl (+51-4)
  • (modified) clang/test/CodeGenHLSL/builtins/StructuredBuffers-constructors.hlsl (+51-3)
  • (modified) clang/test/CodeGenHLSL/builtins/StructuredBuffers-methods-ps.hlsl (+3-3)
  • (modified) clang/test/CodeGenHLSL/static-local-ctor.hlsl (+1-1)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+5)
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 11b1e247237a7..187d3b5ed24a7 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4819,6 +4819,12 @@ def HLSLResourceHandleFromBinding : LangBuiltin<"HLSL_LANG"> {
   let Prototype = "void(...)";
 }
 
+def HLSLResourceHandleFromImplicitBinding : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_resource_handlefromimplicitbinding"];
+  let Attributes = [NoThrow];
+  let Prototype = "void(...)";
+}
+
 def HLSLAll : LangBuiltin<"HLSL_LANG"> {
   let Spellings = ["__builtin_hlsl_all"];
   let Attributes = [NoThrow, Const];
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 5d260acf92abb..bedf541439dbf 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -174,6 +174,8 @@ class SemaHLSL : public SemaBase {
   // buffer which will be created at the end of the translation unit.
   llvm::SmallVector<Decl *> DefaultCBufferDecls;
 
+  uint32_t ImplicitBindingNextOrderID = 0;
+
 private:
   void collectResourceBindingsOnVarDecl(VarDecl *D);
   void collectResourceBindingsOnUserRecordDecl(const VarDecl *VD,
@@ -181,6 +183,11 @@ class SemaHLSL : public SemaBase {
   void processExplicitBindingsOnDecl(VarDecl *D);
 
   void diagnoseAvailabilityViolations(TranslationUnitDecl *TU);
+
+  bool initGlobalResourceDecl(VarDecl *VD);
+  uint32_t getNextImplicitBindingOrderID() {
+    return ImplicitBindingNextOrderID++;
+  }
 };
 
 } // namespace clang
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 5d93df34c66b2..d4a0714da07b3 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -303,6 +303,21 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
         HandleTy, CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic(),
         ArrayRef<Value *>{SpaceOp, RegisterOp, RangeOp, IndexOp, NonUniform});
   }
+  case Builtin::BI__builtin_hlsl_resource_handlefromimplicitbinding: {
+    llvm::Type *HandleTy = CGM.getTypes().ConvertType(E->getType());
+    Value *SpaceOp = EmitScalarExpr(E->getArg(1));
+    Value *RangeOp = EmitScalarExpr(E->getArg(2));
+    Value *IndexOp = EmitScalarExpr(E->getArg(3));
+    Value *OrderID = EmitScalarExpr(E->getArg(4));
+    // FIXME: NonUniformResourceIndex bit is not yet implemented
+    // (llvm/llvm-project#135452)
+    Value *NonUniform =
+        llvm::ConstantInt::get(llvm::Type::getInt1Ty(getLLVMContext()), false);
+    return Builder.CreateIntrinsic(
+        HandleTy,
+        CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic(),
+        ArrayRef<Value *>{OrderID, SpaceOp, RangeOp, IndexOp, NonUniform});
+  }
   case Builtin::BI__builtin_hlsl_all: {
     Value *Op0 = EmitScalarExpr(E->getArg(0));
     return Builder.CreateIntrinsic(
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 4d6db3f5d9f3e..e40864d8ed854 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -119,6 +119,8 @@ class CGHLSLRuntime {
                                    resource_getpointer)
   GENERATE_HLSL_INTRINSIC_FUNCTION(CreateHandleFromBinding,
                                    resource_handlefrombinding)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(CreateHandleFromImplicitBinding,
+                                   resource_handlefromimplicitbinding)
   GENERATE_HLSL_INTRINSIC_FUNCTION(BufferUpdateCounter, resource_updatecounter)
   GENERATE_HLSL_INTRINSIC_FUNCTION(GroupMemoryBarrierWithGroupSync,
                                    group_memory_barrier_with_group_sync)
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 35364a4d6f2ac..d5fbd5f6ecc9f 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -668,6 +668,26 @@ BuiltinTypeDeclBuilder::addHandleConstructorFromBinding() {
       .finalize();
 }
 
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addHandleConstructorFromImplicitBinding() {
+  if (Record->isCompleteDefinition())
+    return *this;
+
+  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
+  ASTContext &AST = SemaRef.getASTContext();
+  QualType HandleType = getResourceHandleField()->getType();
+
+  return BuiltinTypeMethodBuilder(*this, "", AST.VoidTy, false, true)
+      .addParam("spaceNo", AST.UnsignedIntTy)
+      .addParam("range", AST.IntTy)
+      .addParam("index", AST.UnsignedIntTy)
+      .addParam("order_id", AST.UnsignedIntTy)
+      .callBuiltin("__builtin_hlsl_resource_handlefromimplicitbinding",
+                   HandleType, PH::Handle, PH::_0, PH::_1, PH::_2, PH::_3)
+      .assign(PH::Handle, PH::LastStmt)
+      .finalize();
+}
+
 BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addArraySubscriptOperators() {
   ASTContext &AST = Record->getASTContext();
   DeclarationName Subscript =
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index db617dc53c899..a52e2938104c7 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -76,9 +76,10 @@ class BuiltinTypeDeclBuilder {
                   AccessSpecifier Access = AccessSpecifier::AS_private);
   BuiltinTypeDeclBuilder &addArraySubscriptOperators();
 
-  // Builtin types methods
+  // Builtin types constructors
   BuiltinTypeDeclBuilder &addDefaultHandleConstructor();
   BuiltinTypeDeclBuilder &addHandleConstructorFromBinding();
+  BuiltinTypeDeclBuilder &addHandleConstructorFromImplicitBinding();
 
   // Builtin types methods
   BuiltinTypeDeclBuilder &addLoadMethods();
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index f09232a9db4da..38bde7c28e946 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -132,7 +132,8 @@ static BuiltinTypeDeclBuilder setupBufferType(CXXRecordDecl *Decl, Sema &S,
   return BuiltinTypeDeclBuilder(S, Decl)
       .addHandleMember(RC, IsROV, RawBuffer)
       .addDefaultHandleConstructor()
-      .addHandleConstructorFromBinding();
+      .addHandleConstructorFromBinding()
+      .addHandleConstructorFromImplicitBinding();
 }
 
 // This function is responsible for constructing the constraint expression for
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 70aacaa2aadbe..c0669a8d60470 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2427,6 +2427,20 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
     TheCall->setType(ResourceTy);
     break;
   }
+  case Builtin::BI__builtin_hlsl_resource_handlefromimplicitbinding: {
+    ASTContext &AST = SemaRef.getASTContext();
+    if (SemaRef.checkArgCount(TheCall, 5) ||
+        CheckResourceHandle(&SemaRef, TheCall, 0) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(1), AST.UnsignedIntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(2), AST.IntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(3), AST.UnsignedIntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(4), AST.UnsignedIntTy))
+      return true;
+    // use the type of the handle (arg0) as a return type
+    QualType ResourceTy = TheCall->getArg(0)->getType();
+    TheCall->setType(ResourceTy);
+    break;
+  }
   case Builtin::BI__builtin_hlsl_and:
   case Builtin::BI__builtin_hlsl_or: {
     if (SemaRef.checkArgCount(TheCall, 2))
@@ -3258,8 +3272,10 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD,
       VD->getLocation(), SourceLocation(), SourceLocation());
 
   InitializationSequence InitSeq(S, Entity, Kind, Args);
-  ExprResult Init = InitSeq.Perform(S, Entity, Kind, Args);
+  if (InitSeq.Failed())
+    return false;
 
+  ExprResult Init = InitSeq.Perform(S, Entity, Kind, Args);
   if (!Init.get())
     return false;
 
@@ -3269,27 +3285,42 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD,
   return true;
 }
 
-static bool initGlobalResourceDecl(Sema &S, VarDecl *VD) {
+bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
+  std::optional<uint32_t> RegisterSlot;
+  uint32_t SpaceNo = 0;
   HLSLResourceBindingAttr *RBA = VD->getAttr<HLSLResourceBindingAttr>();
-  if (!RBA || RBA->isImplicit())
-    // FIXME: add support for implicit binding (llvm/llvm-project#110722)
-    return false;
+  if (RBA) {
+    if (!RBA->isImplicit())
+      RegisterSlot = RBA->getSlotNumber();
+    SpaceNo = RBA->getSpaceNumber();
+  }
 
-  ASTContext &AST = S.getASTContext();
+  ASTContext &AST = SemaRef.getASTContext();
   uint64_t UIntTySize = AST.getTypeSize(AST.UnsignedIntTy);
   uint64_t IntTySize = AST.getTypeSize(AST.IntTy);
-  Expr *Args[] = {
-      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, RBA->getSlotNumber()),
-                             AST.UnsignedIntTy, SourceLocation()),
-      IntegerLiteral::Create(AST,
-                             llvm::APInt(UIntTySize, RBA->getSpaceNumber()),
-                             AST.UnsignedIntTy, SourceLocation()),
-      IntegerLiteral::Create(AST, llvm::APInt(IntTySize, 1), AST.IntTy,
-                             SourceLocation()),
-      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy,
-                             SourceLocation())};
-
-  return initVarDeclWithCtor(S, VD, Args);
+  IntegerLiteral *One = IntegerLiteral::Create(AST, llvm::APInt(IntTySize, 1),
+                                               AST.IntTy, SourceLocation());
+  IntegerLiteral *Zero = IntegerLiteral::Create(
+      AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy, SourceLocation());
+  IntegerLiteral *Space =
+      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, SpaceNo),
+                             AST.UnsignedIntTy, SourceLocation());
+
+  // resource with explicit binding
+  if (RegisterSlot.has_value()) {
+    IntegerLiteral *RegSlot = IntegerLiteral::Create(
+        AST, llvm::APInt(UIntTySize, RegisterSlot.value()), AST.UnsignedIntTy,
+        SourceLocation());
+    Expr *Args[] = {RegSlot, Space, One, Zero};
+    return initVarDeclWithCtor(SemaRef, VD, Args);
+  }
+
+  // resource with explicit binding
+  IntegerLiteral *OrderId = IntegerLiteral::Create(
+      AST, llvm::APInt(UIntTySize, getNextImplicitBindingOrderID()),
+      AST.UnsignedIntTy, SourceLocation());
+  Expr *Args[] = {Space, One, Zero, OrderId};
+  return initVarDeclWithCtor(SemaRef, VD, Args);
 }
 
 // Returns true if the initialization has been handled.
@@ -3307,8 +3338,9 @@ bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) {
   // FIXME: We currectly support only simple resources - no arrays of resources
   // or resources in user defined structs.
   // (llvm/llvm-project#133835, llvm/llvm-project#133837)
-  if (VD->getType()->isHLSLResourceRecord())
-    return initGlobalResourceDecl(SemaRef, VD);
+  // Initialize resources at the global scope
+  if (VD->hasGlobalStorage() && VD->getType()->isHLSLResourceRecord())
+    return initGlobalResourceDecl(VD);
 
   return false;
 }
diff --git a/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl b/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
index 5fba939d29cfe..99f26473dbb02 100644
--- a/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
@@ -78,5 +78,27 @@ RESOURCE Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]] 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'const element_type &(unsigned int) const'
 // CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'element_type &(unsigned int)'
diff --git a/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl b/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
index 63265a0003582..03bfa6cb26003 100644
--- a/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
@@ -125,6 +125,28 @@ RESOURCE<float> Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]]<element_type> 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // Subscript operators
 
 // CHECK-SUBSCRIPT: CXXMethodDecl {{.*}} operator[] 'const hlsl_device element_type &(unsigned int) const'
diff --git a/clang/test/AST/HLSL/TypedBuffers-AST.hlsl b/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
index 6074c1e8bcdd2..f7b720090d436 100644
--- a/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
@@ -92,6 +92,28 @@ RESOURCE<float> Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]]<element_type> 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // Subsctript operators
 
 // CHECK: CXXMethodDecl {{.*}} operator[] 'const hlsl_device element_type &(unsigned int) const'
diff --git a/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
index b798c2a6d6c4b..1d451acfc6214 100644
--- a/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
+++ b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
@@ -33,7 +33,7 @@ void SecondEntry() {}
 
 // Verify the constructor is alwaysinline
 // NOINLINE: ; Function Attrs: {{.*}}alwaysinline
-// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ev({{.*}} [[CtorAttr:\#[0-9]+]]
+// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ejijj({{.*}} [[CtorAttr:\#[0-9]+]]
 
 // NOINLINE: ; Function Attrs: {{.*}}alwaysinline
 // NOINLINE-NEXT: define internal void @_GLOBAL__sub_I_GlobalConstructorLib.hlsl() [[InitAttr:\#[0-9]+]]
diff --git a/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl b/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
index d7c4b03552cdc..7bc9b624ba9b9 100644
--- a/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
+++ b/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
@@ -55,11 +55,33 @@ export void foo() {
 // CHECK-SAME: i32 noundef %0, i32 noundef %1, i32 noundef %2, i32 noundef %3)
 // CHECK-NEXT: ret void
 
-// Buf2 initialization part 1 - FIXME: constructor with implicit binding does not exist yet; 
-// the global init function currently calls the default RWByteAddressBuffer C1 constructor
-// CHECK: define internal void @__cxx_global_var_init.1()
+// Buf2 initialization part 1 - global init function that calls RWByteAddressBuffer C1 constructor with implicit binding
+// CHECK: define internal void @__cxx_global_var_init.1() #0 {
 // CHECK-NEXT: entry:
-// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL4Buf2)
+// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC1Ejijj(ptr noundef nonnull align 4 dereferenceable(4) @_ZL4Buf2,
+// CHECK-SAME: i32 noundef 0, i32 noundef 1, i32 noundef 0, i32 noundef 0)
+
+// Buf2 initialization part 2 - body of RWByteAddressBuffer C1 constructor with implicit binding that calls the C2 constructor
+// CHECK: define linkonce_odr void @_ZN4hlsl19RWByteAddressBufferC1Ejijj(ptr noundef nonnull align 4 dereferenceable(4) %this,
+// CHECK-SAME: i32 noundef %spaceNo, i32 noundef %range, i32 noundef %index, i32 noundef %order_id)
+// CHECK-NEXT: entry:
+// CHECK-NEXT: %this.addr = alloca ptr, align 4
+// CHECK-NEXT: %spaceNo.addr = alloca i32, align 4
+// CHECK-NEXT: %range.addr = alloca i32, align 4
+// CHECK-NEXT: %index.addr = alloca i32, align 4
+// CHECK-NEXT: %order_id.addr = alloca i32, align 4
+// CHECK-NEXT: store ptr %this, ptr %this.addr, align 4
+// CHECK-NEXT: store i32 %spaceNo, ptr %spaceNo.addr, align 4
+// CHECK-NEXT: store i32 %range, ptr %range.addr, align 4
+// CHECK-NEXT: store i32 %index, ptr %index.addr, align 4
+// CHECK-NEXT: store i32 %order_id, ptr %order_id.addr, align 4
+// CHECK-NEXT: %this1 = load ptr, ptr %this.addr, align 4
+// CHECK-NEXT: %0 = load i32, ptr %spaceNo.addr, align 4
+// CHECK-NEXT: %1 = load i32, ptr %range.addr, align 4
+// CHECK-NEXT: %2 = load i32, ptr %index.addr, align 4
+// CHECK-NEXT: %3 = load i32, ptr %order_id.addr, align 4
+// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC2Ejijj(ptr noundef nonnull align 4 dereferenceable(4) %this1, i32 noundef %0, i32 noundef %1, i32 noundef %2, i32 noundef %3) #4
+// CHECK-NEXT: ret void
 
 // Buf3 initialization part 1 - local variable declared in function foo() is initial...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented May 7, 2025

@llvm/pr-subscribers-hlsl

Author: Helena Kotas (hekota)

Changes

Adds constructor for resources with implicit binding and applies it to all resources without binding at the global scope.
Adds Clang builtin function __builtin_hlsl_resource_handlefromimplicitbinding that gets translated to llvm.dx|spv.resource.handlefromimplicitbinding intrinsic calls. Specific bindings are assigned in DXILResourceImplicitBinding pass.

Design proposals:
https://github.com/llvm/wg-hlsl/blob/main/proposals/0024-implicit-resource-binding.md
https://github.com/llvm/wg-hlsl/blob/main/proposals/0025-resource-constructors.md

One change from the proposals is that the order_id parameter is added onto the constructor. Originally it was supposed to be generated in codegen when the llvm.dx|spv.resource.handlefromimplicitbinding call is emitted, but that is not possible because the call is inside a constructor, and the constructor body is generated once per resource type and not resource instance. So the only way to inject instance-based data like order_id into the llvm.dx|spv.resource.handlefromimplicitbinding call is that it must come in via the constructor argument.

Depends on #138043

Closes #136784


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

18 Files Affected:

  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+7)
  • (modified) clang/lib/CodeGen/CGHLSLBuiltins.cpp (+15)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+2)
  • (modified) clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp (+20)
  • (modified) clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h (+2-1)
  • (modified) clang/lib/Sema/HLSLExternalSemaSource.cpp (+2-1)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+52-20)
  • (modified) clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl (+22)
  • (modified) clang/test/AST/HLSL/StructuredBuffers-AST.hlsl (+22)
  • (modified) clang/test/AST/HLSL/TypedBuffers-AST.hlsl (+22)
  • (modified) clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl (+1-1)
  • (modified) clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl (+51-4)
  • (modified) clang/test/CodeGenHLSL/builtins/RWBuffer-constructor.hlsl (+51-4)
  • (modified) clang/test/CodeGenHLSL/builtins/StructuredBuffers-constructors.hlsl (+51-3)
  • (modified) clang/test/CodeGenHLSL/builtins/StructuredBuffers-methods-ps.hlsl (+3-3)
  • (modified) clang/test/CodeGenHLSL/static-local-ctor.hlsl (+1-1)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+5)
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 11b1e247237a7..187d3b5ed24a7 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4819,6 +4819,12 @@ def HLSLResourceHandleFromBinding : LangBuiltin<"HLSL_LANG"> {
   let Prototype = "void(...)";
 }
 
+def HLSLResourceHandleFromImplicitBinding : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_resource_handlefromimplicitbinding"];
+  let Attributes = [NoThrow];
+  let Prototype = "void(...)";
+}
+
 def HLSLAll : LangBuiltin<"HLSL_LANG"> {
   let Spellings = ["__builtin_hlsl_all"];
   let Attributes = [NoThrow, Const];
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 5d260acf92abb..bedf541439dbf 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -174,6 +174,8 @@ class SemaHLSL : public SemaBase {
   // buffer which will be created at the end of the translation unit.
   llvm::SmallVector<Decl *> DefaultCBufferDecls;
 
+  uint32_t ImplicitBindingNextOrderID = 0;
+
 private:
   void collectResourceBindingsOnVarDecl(VarDecl *D);
   void collectResourceBindingsOnUserRecordDecl(const VarDecl *VD,
@@ -181,6 +183,11 @@ class SemaHLSL : public SemaBase {
   void processExplicitBindingsOnDecl(VarDecl *D);
 
   void diagnoseAvailabilityViolations(TranslationUnitDecl *TU);
+
+  bool initGlobalResourceDecl(VarDecl *VD);
+  uint32_t getNextImplicitBindingOrderID() {
+    return ImplicitBindingNextOrderID++;
+  }
 };
 
 } // namespace clang
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index 5d93df34c66b2..d4a0714da07b3 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -303,6 +303,21 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
         HandleTy, CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic(),
         ArrayRef<Value *>{SpaceOp, RegisterOp, RangeOp, IndexOp, NonUniform});
   }
+  case Builtin::BI__builtin_hlsl_resource_handlefromimplicitbinding: {
+    llvm::Type *HandleTy = CGM.getTypes().ConvertType(E->getType());
+    Value *SpaceOp = EmitScalarExpr(E->getArg(1));
+    Value *RangeOp = EmitScalarExpr(E->getArg(2));
+    Value *IndexOp = EmitScalarExpr(E->getArg(3));
+    Value *OrderID = EmitScalarExpr(E->getArg(4));
+    // FIXME: NonUniformResourceIndex bit is not yet implemented
+    // (llvm/llvm-project#135452)
+    Value *NonUniform =
+        llvm::ConstantInt::get(llvm::Type::getInt1Ty(getLLVMContext()), false);
+    return Builder.CreateIntrinsic(
+        HandleTy,
+        CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic(),
+        ArrayRef<Value *>{OrderID, SpaceOp, RangeOp, IndexOp, NonUniform});
+  }
   case Builtin::BI__builtin_hlsl_all: {
     Value *Op0 = EmitScalarExpr(E->getArg(0));
     return Builder.CreateIntrinsic(
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 4d6db3f5d9f3e..e40864d8ed854 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -119,6 +119,8 @@ class CGHLSLRuntime {
                                    resource_getpointer)
   GENERATE_HLSL_INTRINSIC_FUNCTION(CreateHandleFromBinding,
                                    resource_handlefrombinding)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(CreateHandleFromImplicitBinding,
+                                   resource_handlefromimplicitbinding)
   GENERATE_HLSL_INTRINSIC_FUNCTION(BufferUpdateCounter, resource_updatecounter)
   GENERATE_HLSL_INTRINSIC_FUNCTION(GroupMemoryBarrierWithGroupSync,
                                    group_memory_barrier_with_group_sync)
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 35364a4d6f2ac..d5fbd5f6ecc9f 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -668,6 +668,26 @@ BuiltinTypeDeclBuilder::addHandleConstructorFromBinding() {
       .finalize();
 }
 
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addHandleConstructorFromImplicitBinding() {
+  if (Record->isCompleteDefinition())
+    return *this;
+
+  using PH = BuiltinTypeMethodBuilder::PlaceHolder;
+  ASTContext &AST = SemaRef.getASTContext();
+  QualType HandleType = getResourceHandleField()->getType();
+
+  return BuiltinTypeMethodBuilder(*this, "", AST.VoidTy, false, true)
+      .addParam("spaceNo", AST.UnsignedIntTy)
+      .addParam("range", AST.IntTy)
+      .addParam("index", AST.UnsignedIntTy)
+      .addParam("order_id", AST.UnsignedIntTy)
+      .callBuiltin("__builtin_hlsl_resource_handlefromimplicitbinding",
+                   HandleType, PH::Handle, PH::_0, PH::_1, PH::_2, PH::_3)
+      .assign(PH::Handle, PH::LastStmt)
+      .finalize();
+}
+
 BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addArraySubscriptOperators() {
   ASTContext &AST = Record->getASTContext();
   DeclarationName Subscript =
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index db617dc53c899..a52e2938104c7 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -76,9 +76,10 @@ class BuiltinTypeDeclBuilder {
                   AccessSpecifier Access = AccessSpecifier::AS_private);
   BuiltinTypeDeclBuilder &addArraySubscriptOperators();
 
-  // Builtin types methods
+  // Builtin types constructors
   BuiltinTypeDeclBuilder &addDefaultHandleConstructor();
   BuiltinTypeDeclBuilder &addHandleConstructorFromBinding();
+  BuiltinTypeDeclBuilder &addHandleConstructorFromImplicitBinding();
 
   // Builtin types methods
   BuiltinTypeDeclBuilder &addLoadMethods();
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index f09232a9db4da..38bde7c28e946 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -132,7 +132,8 @@ static BuiltinTypeDeclBuilder setupBufferType(CXXRecordDecl *Decl, Sema &S,
   return BuiltinTypeDeclBuilder(S, Decl)
       .addHandleMember(RC, IsROV, RawBuffer)
       .addDefaultHandleConstructor()
-      .addHandleConstructorFromBinding();
+      .addHandleConstructorFromBinding()
+      .addHandleConstructorFromImplicitBinding();
 }
 
 // This function is responsible for constructing the constraint expression for
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 70aacaa2aadbe..c0669a8d60470 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2427,6 +2427,20 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
     TheCall->setType(ResourceTy);
     break;
   }
+  case Builtin::BI__builtin_hlsl_resource_handlefromimplicitbinding: {
+    ASTContext &AST = SemaRef.getASTContext();
+    if (SemaRef.checkArgCount(TheCall, 5) ||
+        CheckResourceHandle(&SemaRef, TheCall, 0) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(1), AST.UnsignedIntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(2), AST.IntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(3), AST.UnsignedIntTy) ||
+        CheckArgTypeMatches(&SemaRef, TheCall->getArg(4), AST.UnsignedIntTy))
+      return true;
+    // use the type of the handle (arg0) as a return type
+    QualType ResourceTy = TheCall->getArg(0)->getType();
+    TheCall->setType(ResourceTy);
+    break;
+  }
   case Builtin::BI__builtin_hlsl_and:
   case Builtin::BI__builtin_hlsl_or: {
     if (SemaRef.checkArgCount(TheCall, 2))
@@ -3258,8 +3272,10 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD,
       VD->getLocation(), SourceLocation(), SourceLocation());
 
   InitializationSequence InitSeq(S, Entity, Kind, Args);
-  ExprResult Init = InitSeq.Perform(S, Entity, Kind, Args);
+  if (InitSeq.Failed())
+    return false;
 
+  ExprResult Init = InitSeq.Perform(S, Entity, Kind, Args);
   if (!Init.get())
     return false;
 
@@ -3269,27 +3285,42 @@ static bool initVarDeclWithCtor(Sema &S, VarDecl *VD,
   return true;
 }
 
-static bool initGlobalResourceDecl(Sema &S, VarDecl *VD) {
+bool SemaHLSL::initGlobalResourceDecl(VarDecl *VD) {
+  std::optional<uint32_t> RegisterSlot;
+  uint32_t SpaceNo = 0;
   HLSLResourceBindingAttr *RBA = VD->getAttr<HLSLResourceBindingAttr>();
-  if (!RBA || RBA->isImplicit())
-    // FIXME: add support for implicit binding (llvm/llvm-project#110722)
-    return false;
+  if (RBA) {
+    if (!RBA->isImplicit())
+      RegisterSlot = RBA->getSlotNumber();
+    SpaceNo = RBA->getSpaceNumber();
+  }
 
-  ASTContext &AST = S.getASTContext();
+  ASTContext &AST = SemaRef.getASTContext();
   uint64_t UIntTySize = AST.getTypeSize(AST.UnsignedIntTy);
   uint64_t IntTySize = AST.getTypeSize(AST.IntTy);
-  Expr *Args[] = {
-      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, RBA->getSlotNumber()),
-                             AST.UnsignedIntTy, SourceLocation()),
-      IntegerLiteral::Create(AST,
-                             llvm::APInt(UIntTySize, RBA->getSpaceNumber()),
-                             AST.UnsignedIntTy, SourceLocation()),
-      IntegerLiteral::Create(AST, llvm::APInt(IntTySize, 1), AST.IntTy,
-                             SourceLocation()),
-      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy,
-                             SourceLocation())};
-
-  return initVarDeclWithCtor(S, VD, Args);
+  IntegerLiteral *One = IntegerLiteral::Create(AST, llvm::APInt(IntTySize, 1),
+                                               AST.IntTy, SourceLocation());
+  IntegerLiteral *Zero = IntegerLiteral::Create(
+      AST, llvm::APInt(UIntTySize, 0), AST.UnsignedIntTy, SourceLocation());
+  IntegerLiteral *Space =
+      IntegerLiteral::Create(AST, llvm::APInt(UIntTySize, SpaceNo),
+                             AST.UnsignedIntTy, SourceLocation());
+
+  // resource with explicit binding
+  if (RegisterSlot.has_value()) {
+    IntegerLiteral *RegSlot = IntegerLiteral::Create(
+        AST, llvm::APInt(UIntTySize, RegisterSlot.value()), AST.UnsignedIntTy,
+        SourceLocation());
+    Expr *Args[] = {RegSlot, Space, One, Zero};
+    return initVarDeclWithCtor(SemaRef, VD, Args);
+  }
+
+  // resource with explicit binding
+  IntegerLiteral *OrderId = IntegerLiteral::Create(
+      AST, llvm::APInt(UIntTySize, getNextImplicitBindingOrderID()),
+      AST.UnsignedIntTy, SourceLocation());
+  Expr *Args[] = {Space, One, Zero, OrderId};
+  return initVarDeclWithCtor(SemaRef, VD, Args);
 }
 
 // Returns true if the initialization has been handled.
@@ -3307,8 +3338,9 @@ bool SemaHLSL::ActOnUninitializedVarDecl(VarDecl *VD) {
   // FIXME: We currectly support only simple resources - no arrays of resources
   // or resources in user defined structs.
   // (llvm/llvm-project#133835, llvm/llvm-project#133837)
-  if (VD->getType()->isHLSLResourceRecord())
-    return initGlobalResourceDecl(SemaRef, VD);
+  // Initialize resources at the global scope
+  if (VD->hasGlobalStorage() && VD->getType()->isHLSLResourceRecord())
+    return initGlobalResourceDecl(VD);
 
   return false;
 }
diff --git a/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl b/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
index 5fba939d29cfe..99f26473dbb02 100644
--- a/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/ByteAddressBuffers-AST.hlsl
@@ -78,5 +78,27 @@ RESOURCE Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]] 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} 'hlsl::[[RESOURCE]]' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'const element_type &(unsigned int) const'
 // CHECK-NOSUBSCRIPT-NOT: CXXMethodDecl {{.*}} operator[] 'element_type &(unsigned int)'
diff --git a/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl b/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
index 63265a0003582..03bfa6cb26003 100644
--- a/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/StructuredBuffers-AST.hlsl
@@ -125,6 +125,28 @@ RESOURCE<float> Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]]<element_type> 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // Subscript operators
 
 // CHECK-SUBSCRIPT: CXXMethodDecl {{.*}} operator[] 'const hlsl_device element_type &(unsigned int) const'
diff --git a/clang/test/AST/HLSL/TypedBuffers-AST.hlsl b/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
index 6074c1e8bcdd2..f7b720090d436 100644
--- a/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
+++ b/clang/test/AST/HLSL/TypedBuffers-AST.hlsl
@@ -92,6 +92,28 @@ RESOURCE<float> Buffer;
 // CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
 // CHECK-NEXT: AlwaysInlineAttr
 
+// Constructor from implicit binding
+
+// CHECK: CXXConstructorDecl {{.*}} [[RESOURCE]]<element_type> 'void (unsigned int, int, unsigned int, unsigned int)' inline
+// CHECK-NEXT: ParmVarDecl {{.*}} spaceNo 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} range 'int'
+// CHECK-NEXT: ParmVarDecl {{.*}} index 'unsigned int'
+// CHECK-NEXT: ParmVarDecl {{.*}} order_id 'unsigned int'
+// CHECK-NEXT: CompoundStmt {{.*}}
+// CHECK-NEXT: BinaryOperator {{.*}} '='
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: CallExpr {{.*}} '__hlsl_resource_t
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <BuiltinFnToFnPtr>
+// CHECK-NEXT: DeclRefExpr {{.*}} '<builtin fn type>' Function {{.*}} '__builtin_hlsl_resource_handlefromimplicitbinding'
+// CHECK-NEXT: MemberExpr {{.*}} lvalue .__handle
+// CHECK-NEXT: CXXThisExpr {{.*}} '[[RESOURCE]]<element_type>' lvalue implicit this
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'spaceNo' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' ParmVar {{.*}} 'range' 'int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'index' 'unsigned int'
+// CHECK-NEXT: DeclRefExpr {{.*}} 'unsigned int' ParmVar {{.*}} 'order_id' 'unsigned int'
+// CHECK-NEXT: AlwaysInlineAttr
+
 // Subsctript operators
 
 // CHECK: CXXMethodDecl {{.*}} operator[] 'const hlsl_device element_type &(unsigned int) const'
diff --git a/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
index b798c2a6d6c4b..1d451acfc6214 100644
--- a/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
+++ b/clang/test/CodeGenHLSL/GlobalConstructorLib.hlsl
@@ -33,7 +33,7 @@ void SecondEntry() {}
 
 // Verify the constructor is alwaysinline
 // NOINLINE: ; Function Attrs: {{.*}}alwaysinline
-// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ev({{.*}} [[CtorAttr:\#[0-9]+]]
+// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ejijj({{.*}} [[CtorAttr:\#[0-9]+]]
 
 // NOINLINE: ; Function Attrs: {{.*}}alwaysinline
 // NOINLINE-NEXT: define internal void @_GLOBAL__sub_I_GlobalConstructorLib.hlsl() [[InitAttr:\#[0-9]+]]
diff --git a/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl b/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
index d7c4b03552cdc..7bc9b624ba9b9 100644
--- a/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
+++ b/clang/test/CodeGenHLSL/builtins/ByteAddressBuffers-constructors.hlsl
@@ -55,11 +55,33 @@ export void foo() {
 // CHECK-SAME: i32 noundef %0, i32 noundef %1, i32 noundef %2, i32 noundef %3)
 // CHECK-NEXT: ret void
 
-// Buf2 initialization part 1 - FIXME: constructor with implicit binding does not exist yet; 
-// the global init function currently calls the default RWByteAddressBuffer C1 constructor
-// CHECK: define internal void @__cxx_global_var_init.1()
+// Buf2 initialization part 1 - global init function that calls RWByteAddressBuffer C1 constructor with implicit binding
+// CHECK: define internal void @__cxx_global_var_init.1() #0 {
 // CHECK-NEXT: entry:
-// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC1Ev(ptr noundef nonnull align 4 dereferenceable(4) @_ZL4Buf2)
+// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC1Ejijj(ptr noundef nonnull align 4 dereferenceable(4) @_ZL4Buf2,
+// CHECK-SAME: i32 noundef 0, i32 noundef 1, i32 noundef 0, i32 noundef 0)
+
+// Buf2 initialization part 2 - body of RWByteAddressBuffer C1 constructor with implicit binding that calls the C2 constructor
+// CHECK: define linkonce_odr void @_ZN4hlsl19RWByteAddressBufferC1Ejijj(ptr noundef nonnull align 4 dereferenceable(4) %this,
+// CHECK-SAME: i32 noundef %spaceNo, i32 noundef %range, i32 noundef %index, i32 noundef %order_id)
+// CHECK-NEXT: entry:
+// CHECK-NEXT: %this.addr = alloca ptr, align 4
+// CHECK-NEXT: %spaceNo.addr = alloca i32, align 4
+// CHECK-NEXT: %range.addr = alloca i32, align 4
+// CHECK-NEXT: %index.addr = alloca i32, align 4
+// CHECK-NEXT: %order_id.addr = alloca i32, align 4
+// CHECK-NEXT: store ptr %this, ptr %this.addr, align 4
+// CHECK-NEXT: store i32 %spaceNo, ptr %spaceNo.addr, align 4
+// CHECK-NEXT: store i32 %range, ptr %range.addr, align 4
+// CHECK-NEXT: store i32 %index, ptr %index.addr, align 4
+// CHECK-NEXT: store i32 %order_id, ptr %order_id.addr, align 4
+// CHECK-NEXT: %this1 = load ptr, ptr %this.addr, align 4
+// CHECK-NEXT: %0 = load i32, ptr %spaceNo.addr, align 4
+// CHECK-NEXT: %1 = load i32, ptr %range.addr, align 4
+// CHECK-NEXT: %2 = load i32, ptr %index.addr, align 4
+// CHECK-NEXT: %3 = load i32, ptr %order_id.addr, align 4
+// CHECK-NEXT: call void @_ZN4hlsl19RWByteAddressBufferC2Ejijj(ptr noundef nonnull align 4 dereferenceable(4) %this1, i32 noundef %0, i32 noundef %1, i32 noundef %2, i32 noundef %3) #4
+// CHECK-NEXT: ret void
 
 // Buf3 initialization part 1 - local variable declared in function foo() is initial...
[truncated]

@@ -33,7 +33,7 @@ void SecondEntry() {}

// Verify the constructor is alwaysinline
// NOINLINE: ; Function Attrs: {{.*}}alwaysinline
// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ev({{.*}} [[CtorAttr:\#[0-9]+]]
// NOINLINE-NEXT: define linkonce_odr void @_ZN4hlsl8RWBufferIfEC2Ejijj({{.*}} [[CtorAttr:\#[0-9]+]]
Copy link
Member Author

@hekota hekota May 7, 2025

Choose a reason for hiding this comment

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

This is changing the RWBuffer<float> Buffer; initialization from the default constructor (init handle to poison value) to the constructor with implicit binding. Then jijj suffix is Itanium mangling of the constructor arguments (unsigned int, int, unsigned int, unsigned int).

@@ -21,7 +21,7 @@ void InitBuf(RWBuffer<int> buf) {
// CHECK-NEXT: br i1 [[Tmp3]]
// CHECK-NOT: _Init_thread_header
// CHECK: init.check:
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1Ev
// CHECK-NEXT: call void @_ZN4hlsl8RWBufferIiEC1Ejijj
Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above - updates the RWBuffer init from the default constructor to the constructor with implicit binding.

@hekota hekota requested a review from bogner May 9, 2025 21:15
@hekota hekota requested a review from V-FEXrt May 9, 2025 21:15
@@ -668,6 +668,26 @@ BuiltinTypeDeclBuilder::addHandleConstructorFromBinding() {
.finalize();
}

BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addHandleConstructorFromImplicitBinding() {
if (Record->isCompleteDefinition())
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain why this is needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

If I recall correctly, it has something to do with precompiled headers. But now when I tried to remove to see what fails, all of the test pass! So maybe it is not needed anymore?

I would prefer to keep it in for now for consistency with the other constructor and method building functions. I will review all places where this is used and remove them all at once if it is really no longer needed.

// CHECK: define linkonce_odr void @_ZN4hlsl19RWByteAddressBufferC1Ejijj(ptr noundef nonnull align 4 dereferenceable(4) %this,
// CHECK-SAME: i32 noundef %spaceNo, i32 noundef %range, i32 noundef %index, i32 noundef %order_id)
// CHECK-NEXT: entry:
// CHECK-NEXT: %this.addr = alloca ptr, align 4
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this test too fragile? In other tests we don't actually want to verify all the alloca/loads since they aren't actually doing the work we care about and will get optimized away. Is that the case here too?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I am trying to verify that the arguments are actually passed though at the correct places, but maybe it is an overkill and makes the tests too fragile. I will consider simplifying it by removing the alloca/store/load calls.

@hekota hekota changed the base branch from users/hekota/pr138043-implicit-binding-pass to main May 13, 2025 06:05
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 llvm:ir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL] Implement resource constructor with implicit binding
3 participants