diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -100,6 +100,7 @@ case Decl::ObjCTypeParam: case Decl::Binding: case Decl::UnresolvedUsingIfExists: + case Decl::HLSLBuffer: llvm_unreachable("Declaration should not be in declstmts!"); case Decl::Record: // struct/union/class X; case Decl::CXXRecord: // struct/union/class X; [C++] @@ -179,9 +180,6 @@ EmitVariablyModifiedType(Ty); return; } - case Decl::HLSLBuffer: - // FIXME: add codegen for HLSLBuffer. - return; } } diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h --- a/clang/lib/CodeGen/CGHLSLRuntime.h +++ b/clang/lib/CodeGen/CGHLSLRuntime.h @@ -19,13 +19,25 @@ #include "clang/Basic/HLSLRuntime.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +#include + namespace llvm { class GlobalVariable; class Function; +class StructType; } // namespace llvm + namespace clang { class VarDecl; class ParmVarDecl; +class HLSLBufferDecl; +class CallExpr; +class Type; +class DeclContext; class FunctionDecl; @@ -34,6 +46,19 @@ class CodeGenModule; class CGHLSLRuntime { +public: + struct Buffer { + Buffer(const HLSLBufferDecl *D); + llvm::StringRef Name; + // IsCBuffer - Whether the buffer is a cbuffer (and not a tbuffer). + bool IsCBuffer; + llvm::Optional Reg; + unsigned Space; + // Global variable and offset for each constant. + std::vector> Constants; + llvm::StructType *LayoutStruct = nullptr; + }; + protected: CodeGenModule &CGM; uint32_t ResourceCounters[static_cast( @@ -48,11 +73,18 @@ void annotateHLSLResource(const VarDecl *D, llvm::GlobalVariable *GV); void generateGlobalCtorDtorCalls(); + void addBuffer(const HLSLBufferDecl *D); void finishCodeGen(); void setHLSLEntryAttributes(const FunctionDecl *FD, llvm::Function *Fn); void emitEntryFunction(const FunctionDecl *FD, llvm::Function *Fn); + void setHLSLFunctionAttributes(llvm::Function *, const FunctionDecl *); + +private: + void addConstant(VarDecl *D, Buffer &CB); + void addBufferDecls(const DeclContext *DC, Buffer &CB); + llvm::SmallVector Buffers; }; } // namespace CodeGen diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "CGHLSLRuntime.h" +#include "CGDebugInfo.h" #include "CodeGenModule.h" #include "clang/AST/Decl.h" #include "clang/Basic/TargetOptions.h" @@ -27,6 +28,7 @@ using namespace llvm; namespace { + void addDxilValVersion(StringRef ValVersionStr, llvm::Module &M) { // The validation of ValVersionStr is done at HLSLToolChain::TranslateArgs. // Assume ValVersionStr is legal here. @@ -51,9 +53,116 @@ StringRef Key = "dx.disable_optimizations"; M.addModuleFlag(llvm::Module::ModFlagBehavior::Override, Key, 1); } +// cbuffer will be translated into global variable in special address space. +// If translate into C, +// cbuffer A { +// float a; +// float b; +// } +// float foo() { return a + b; } +// +// will be translated into +// +// struct A { +// float a; +// float b; +// } cbuffer_A __attribute__((address_space(4))); +// float foo() { return cbuffer_A.a + cbuffer_A.b; } +// +// layoutBuffer will create the struct A type. +// replaceBuffer will replace use of global variable a and b with cbuffer_A.a +// and cbuffer_A.b. +// +void layoutBuffer(CGHLSLRuntime::Buffer &Buf, const DataLayout &DL) { + if (Buf.Constants.empty()) + return; + + std::vector EltTys; + for (auto &Const : Buf.Constants) { + GlobalVariable *GV = Const.first; + Const.second = EltTys.size(); + llvm::Type *Ty = GV->getValueType(); + EltTys.emplace_back(Ty); + } + Buf.LayoutStruct = llvm::StructType::get(EltTys[0]->getContext(), EltTys); +} + +GlobalVariable *replaceBuffer(CGHLSLRuntime::Buffer &Buf) { + // Create global variable for CB. + GlobalVariable *CBGV = + new GlobalVariable(Buf.LayoutStruct, /*isConstant*/ true, + GlobalValue::LinkageTypes::ExternalLinkage, nullptr, + Buf.Name + (Buf.IsCBuffer ? ".cb." : ".tb."), + GlobalValue::NotThreadLocal); + + IRBuilder<> B(CBGV->getContext()); + Value *ZeroIdx = B.getInt32(0); + // Replace Const use with CB use. + for (auto &Const : Buf.Constants) { + llvm::Type *EltTy = Buf.LayoutStruct->getElementType(Const.second); + GlobalVariable *GV = Const.first; + unsigned Offset = Const.second; + + Value *GEP = + B.CreateGEP(Buf.LayoutStruct, CBGV, {ZeroIdx, B.getInt32(Offset)}); + + llvm::Type *GVTy = GV->getValueType(); + assert(EltTy == GVTy && "constant type mismatch"); + + // Replace. + GV->replaceAllUsesWith(GEP); + // Erase GV. + GV->removeDeadConstantUsers(); + GV->eraseFromParent(); + } + return CBGV; +} } // namespace +void CGHLSLRuntime::addConstant(VarDecl *D, Buffer &CB) { + if (D->getStorageClass() == SC_Static) { + // For static inside cbuffer, take as global static. + // Don't add to cbuffer. + CGM.EmitGlobal(D); + return; + } + + auto *GV = cast(CGM.GetAddrOfGlobalVar(D)); + // Add debug info for constVal. + if (CGDebugInfo *DI = CGM.getModuleDebugInfo()) + if (CGM.getCodeGenOpts().getDebugInfo() >= + codegenoptions::DebugInfoKind::LimitedDebugInfo) + DI->EmitGlobalVariable(cast(GV), D); + + // FIXME: support packoffset. + // See https://github.com/llvm/llvm-project/issues/57914. + uint32_t Offset = 0; + bool HasUserOffset = false; + + unsigned LowerBound = HasUserOffset ? Offset : UINT_MAX; + CB.Constants.emplace_back(std::make_pair(GV, LowerBound)); +} + +void CGHLSLRuntime::addBufferDecls(const DeclContext *DC, Buffer &CB) { + for (Decl *it : DC->decls()) { + if (auto *ConstDecl = dyn_cast(it)) { + addConstant(ConstDecl, CB); + } else if (isa(it)) { + // Nothing to do for this declaration. + } else if (isa(it)) { + // A function within an cbuffer is effectively a top-level function, + // as it only refers to globally scoped declarations. + CGM.EmitTopLevelDecl(it); + } + } +} + +void CGHLSLRuntime::addBuffer(const HLSLBufferDecl *D) { + Buffers.emplace_back(Buffer(D)); + addBufferDecls(D, Buffers.back()); +} + void CGHLSLRuntime::finishCodeGen() { auto &TargetOpts = CGM.getTarget().getTargetOpts(); llvm::Module &M = CGM.getModule(); @@ -64,6 +173,32 @@ generateGlobalCtorDtorCalls(); if (CGM.getCodeGenOpts().OptimizationLevel == 0) addDisableOptimizations(M); + + const DataLayout &DL = M.getDataLayout(); + + for (auto &Buf : Buffers) { + layoutBuffer(Buf, DL); + GlobalVariable *GV = replaceBuffer(Buf); + M.getGlobalList().push_back(GV); + // FIXME: generate resource binding. + // See https://github.com/llvm/llvm-project/issues/57915. + } +} + +CGHLSLRuntime::Buffer::Buffer(const HLSLBufferDecl *D) { + Name = D->getName(); + IsCBuffer = D->isCBuffer(); + if (auto *Binding = D->getAttr()) { + llvm::APInt RegInt(64, 0); + Binding->getSlot().substr(1).getAsInteger(10, RegInt); + Reg = RegInt.getLimitedValue(); + + llvm::APInt SpaceInt(64, 0); + Binding->getSpace().substr(5).getAsInteger(10, RegInt); + Space = SpaceInt.getLimitedValue(); + } else { + Space = 0; + } } void CGHLSLRuntime::annotateHLSLResource(const VarDecl *D, GlobalVariable *GV) { diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -6451,6 +6451,10 @@ DI->EmitAndRetainType(getContext().getEnumType(cast(D))); break; + case Decl::HLSLBuffer: + getHLSLRuntime().addBuffer(cast(D)); + break; + default: // Make sure we handled everything we should, every other kind is a // non-top-level decl. FIXME: Would be nice to have an isTopLevelDeclKind diff --git a/clang/test/CodeGenHLSL/cbuf.hlsl b/clang/test/CodeGenHLSL/cbuf.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/cbuf.hlsl @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: dxil-pc-shadermodel6.3-library %s \ +// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s + +// CHECK: @[[CB:.+]] = external constant { float, double } +cbuffer A : register(b0, space1) { + float a; + double b; +} + +// CHECK: @[[TB:.+]] = external constant { float, double } +tbuffer A : register(t2, space1) { + float c; + double d; +} + +float foo() { +// CHECK: load float, ptr @[[CB]], align 4 +// CHECK: load double, ptr getelementptr inbounds ({ float, double }, ptr @[[CB]], i32 0, i32 1), align 8 +// CHECK: load float, ptr @[[TB]], align 4 +// CHECK: load double, ptr getelementptr inbounds ({ float, double }, ptr @[[TB]], i32 0, i32 1), align 8 + return a + b + c*d; +} diff --git a/clang/test/CodeGenHLSL/cbuf_in_namespace.hlsl b/clang/test/CodeGenHLSL/cbuf_in_namespace.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/cbuf_in_namespace.hlsl @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: dxil-pc-shadermodel6.3-library %s \ +// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s + +// Make sure cbuffer inside namespace works. +// CHECK: @[[CB:.+]] = external constant { float } +// CHECK: @[[TB:.+]] = external constant { float } +namespace n0 { +namespace n1 { + cbuffer A { + float a; + } +} + tbuffer B { + float b; + } +} + +float foo() { +// CHECK: load float, ptr @[[CB]], align 4 +// CHECK: load float, ptr @[[TB]], align 4 + return n0::n1::a + n0::b; +} diff --git a/clang/test/CodeGenHLSL/static_global_and_function_in_cb.hlsl b/clang/test/CodeGenHLSL/static_global_and_function_in_cb.hlsl new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenHLSL/static_global_and_function_in_cb.hlsl @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple \ +// RUN: dxil-pc-shadermodel6.3-library %s \ +// RUN: -emit-llvm -disable-llvm-passes -o - | FileCheck %s + +// CHECK-DAG: @[[CB:.+]] = external constant { float } + +cbuffer A { + float a; + // CHECK-DAG:@b = internal global float 3.000000e+00, align 4 + static float b = 3; + // CHECK:load float, ptr @[[CB]], align 4 + // CHECK:load float, ptr @b, align 4 + float foo() { return a + b; } +} + +float bar() { + return foo(); +}