blob: f86cb00c204f8ce5e09c07f84cdf9fadda6b8f35 [file] [edit]
//===----- CGHLSLRuntime.cpp - Interface to HLSL Runtimes -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This provides an abstract class for HLSL code generation. Concrete
// subclasses of this implement code generation for specific HLSL
// runtime libraries.
//
//===----------------------------------------------------------------------===//
#include "CGHLSLRuntime.h"
#include "CGDebugInfo.h"
#include "CGRecordLayout.h"
#include "CodeGenFunction.h"
#include "CodeGenModule.h"
#include "HLSLBufferLayoutBuilder.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/HLSLResource.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/DiagnosticDriver.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/HLSL/RootSignatureMetadata.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include <cstdint>
#include <optional>
using namespace clang;
using namespace CodeGen;
using namespace clang::hlsl;
using namespace llvm;
using llvm::hlsl::CBufferRowSizeInBytes;
namespace {
void addDxilValVersion(StringRef ValVersionStr, llvm::Module &M) {
// The validation of ValVersionStr is done at HLSLToolChain::TranslateArgs.
// Assume ValVersionStr is legal here.
VersionTuple Version;
if (Version.tryParse(ValVersionStr) || Version.getBuild() ||
Version.getSubminor() || !Version.getMinor()) {
return;
}
uint64_t Major = Version.getMajor();
uint64_t Minor = *Version.getMinor();
auto &Ctx = M.getContext();
IRBuilder<> B(M.getContext());
MDNode *Val = MDNode::get(Ctx, {ConstantAsMetadata::get(B.getInt32(Major)),
ConstantAsMetadata::get(B.getInt32(Minor))});
StringRef DXILValKey = "dx.valver";
auto *DXILValMD = M.getOrInsertNamedMetadata(DXILValKey);
DXILValMD->addOperand(Val);
}
void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer,
ArrayRef<llvm::hlsl::rootsig::RootElement> Elements,
llvm::Function *Fn, llvm::Module &M) {
auto &Ctx = M.getContext();
llvm::hlsl::rootsig::MetadataBuilder RSBuilder(Ctx, Elements);
MDNode *RootSignature = RSBuilder.BuildRootSignature();
ConstantAsMetadata *Version = ConstantAsMetadata::get(ConstantInt::get(
llvm::Type::getInt32Ty(Ctx), llvm::to_underlying(RootSigVer)));
ValueAsMetadata *EntryFunc = Fn ? ValueAsMetadata::get(Fn) : nullptr;
MDNode *MDVals = MDNode::get(Ctx, {EntryFunc, RootSignature, Version});
StringRef RootSignatureValKey = "dx.rootsignatures";
auto *RootSignatureValMD = M.getOrInsertNamedMetadata(RootSignatureValKey);
RootSignatureValMD->addOperand(MDVals);
}
static void copyGlobalResource(CodeGenFunction &CGF, const VarDecl *ResourceVD,
AggValueSlot &DestSlot) {
GlobalVariable *ResGV =
cast<GlobalVariable>(CGF.CGM.GetAddrOfGlobalVar(ResourceVD));
assert(ResGV && "expected valid global variable");
CGF.Builder.CreateStore(ResGV, DestSlot.getAddress());
}
// Given a MemberExpr of a resource or resource array type, find the parent
// VarDecl of the struct or class instance that contains this resource and
// build the full resource name based on the member access path.
//
// For example, for a member access like "myStructArray[0].memberA",
// this function will find the VarDecl of "myStructArray" and use the
// EmbeddedResourceNameBuilder to build the resource name
// "myStructArray.0.memberA".
//
// This also works for a record type expression that has some embedded
// resources. It finds the parent VarDecl of that record and builds a partial
// name which is the prefix of the resource globals associated with the
// declaration.
static const VarDecl *findStructResourceParentDeclAndBuildName(
const Expr *E, EmbeddedResourceNameBuilder &NameBuilder) {
SmallVector<const Expr *> WorkList;
const VarDecl *VD = nullptr;
for (;;) {
if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
assert(isa<VarDecl>(DRE->getDecl()) &&
"member expr base is not a var decl");
VD = cast<VarDecl>(DRE->getDecl());
NameBuilder.pushName(VD->getName());
break;
}
WorkList.push_back(E);
if (const auto *MExp = dyn_cast<MemberExpr>(E))
E = MExp->getBase();
else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
E = ICE->getSubExpr();
else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
E = ASE->getBase();
else if (isa<CXXThisExpr>(E))
// Resource member access on "this" pointer not yet implemented
// (llvm/llvm-project#190299)
return nullptr;
else
llvm_unreachable("unexpected expr type in resource member access");
assert(E && "expected valid expression");
}
while (!WorkList.empty()) {
E = WorkList.pop_back_val();
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
NameBuilder.pushName(
ME->getMemberNameInfo().getName().getAsIdentifierInfo()->getName());
} else if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E)) {
if (ICE->getCastKind() == CK_UncheckedDerivedToBase) {
CXXRecordDecl *DerivedRD =
ICE->getSubExpr()->getType()->getAsCXXRecordDecl();
CXXRecordDecl *BaseRD = ICE->getType()->getAsCXXRecordDecl();
NameBuilder.pushBaseNameHierarchy(DerivedRD, BaseRD);
}
} else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) {
const Expr *IdxExpr = ASE->getIdx();
std::optional<llvm::APSInt> Value =
IdxExpr->getIntegerConstantExpr(VD->getASTContext());
assert(Value &&
"expected constant index in struct with resource array access");
NameBuilder.pushArrayIndex(Value->getZExtValue());
} else {
llvm_unreachable("unexpected expr type in resource member access");
}
}
return VD;
}
// Given a MemberExpr of a resource or resource array type, find the
// corresponding global resource declaration associated with the owning struct
// or class instance via HLSLAssociatedResourceDeclAttr.
static const VarDecl *
findAssociatedResourceDeclForStruct(ASTContext &AST, const MemberExpr *ME) {
EmbeddedResourceNameBuilder NameBuilder;
const VarDecl *ParentVD =
findStructResourceParentDeclAndBuildName(ME, NameBuilder);
if (!ParentVD)
return nullptr;
if (!ParentVD->hasGlobalStorage())
return nullptr;
IdentifierInfo *II = NameBuilder.getNameAsIdentifier(AST);
for (const Attr *A : ParentVD->getAttrs()) {
if (const auto *ADA = dyn_cast<HLSLAssociatedResourceDeclAttr>(A)) {
VarDecl *AssocResVD = ADA->getResDecl();
if (AssocResVD->getIdentifier() == II)
return AssocResVD;
}
}
return nullptr;
}
void addSourceInfo(CodeGenModule &CGM, llvm::Module &M) {
auto &SM = CGM.getContext().getSourceManager();
auto &Macros = CGM.getPreprocessorOpts().Macros;
auto &CodeGenOpts = CGM.getCodeGenOpts();
auto &Ctx = M.getContext();
// Names and content of shader source code files.
llvm::NamedMDNode *DXContents =
M.getOrInsertNamedMetadata("dx.source.contents");
auto addFile = [&](const std::pair<StringRef, StringRef> &NameContent) {
llvm::MDTuple *FileInfo =
llvm::MDNode::get(Ctx, {llvm::MDString::get(Ctx, NameContent.first),
llvm::MDString::get(Ctx, NameContent.second)});
DXContents->addOperand(FileInfo);
};
bool Invalid = false;
const SrcMgr::SLocEntry *MainLocEntry =
&SM.getSLocEntry(SM.getMainFileID(), &Invalid);
assert(!Invalid && "Main file SLocEntry must not be invalid!");
const SrcMgr::ContentCache &MainCCEntry =
MainLocEntry->getFile().getContentCache();
SmallVector<std::pair<std::string, StringRef>> Files;
std::optional<SmallString<256>> MainFileName;
Files.reserve(SM.local_sloc_entry_size());
for (unsigned I : llvm::seq(SM.local_sloc_entry_size())) {
const SrcMgr::SLocEntry &LocEntry = SM.getLocalSLocEntry(I);
if (!LocEntry.isFile())
continue;
const SrcMgr::FileInfo &FInfo = LocEntry.getFile();
if (isSystem(FInfo.getFileCharacteristic()))
continue;
const SrcMgr::ContentCache &CCEntry = FInfo.getContentCache();
OptionalFileEntryRef FEntry = CCEntry.OrigEntry;
if (!FEntry)
continue;
llvm::SmallString<256> Path = FEntry->getName();
llvm::sys::path::native(Path);
std::optional<llvm::MemoryBufferRef> Buffer = CCEntry.getBufferOrNone(
SM.getDiagnostics(), SM.getFileManager(), SourceLocation());
if (!Buffer) {
SM.getDiagnostics().Report(diag::warn_hlsl_failed_to_embed_source)
<< Path;
continue;
}
if (&MainCCEntry != &CCEntry) {
Files.emplace_back(Path, Buffer->getBuffer());
} else {
// Main file should be at first position.
addFile(std::make_pair(Path, Buffer->getBuffer()));
MainFileName.emplace(Path);
}
}
assert(MainFileName && "Main file not found.");
// Files other that main one should be sorted by name.
llvm::sort(Files);
#ifndef NDEBUG
for (unsigned I = 1; I < Files.size(); ++I)
assert((Files[I - 1].first != Files[I].first) &&
"duplicate files in dx.source.contents");
#endif
llvm::for_each(Files, addFile);
SmallVector<llvm::Metadata *> Defines;
Defines.reserve(Macros.size());
for (const auto &Macro : Macros) {
// Ignore undefs.
if (!Macro.second)
Defines.emplace_back(llvm::MDString::get(Ctx, Macro.first));
}
M.getOrInsertNamedMetadata("dx.source.defines")
->addOperand(llvm::MDNode::get(Ctx, Defines));
if (!CodeGenOpts.MainFileName.empty())
llvm::sys::path::native(CodeGenOpts.MainFileName, *MainFileName);
M.getOrInsertNamedMetadata("dx.source.mainFileName")
->addOperand(
llvm::MDNode::get(Ctx, llvm::MDString::get(Ctx, *MainFileName)));
SmallVector<llvm::Metadata *> Args;
Args.reserve(CodeGenOpts.HLSLParsedCommandLine.size());
if (!CodeGenOpts.HLSLParsedCommandLine.empty())
for (const auto &Arg : llvm::drop_begin(CodeGenOpts.HLSLParsedCommandLine))
Args.push_back(llvm::MDString::get(Ctx, Arg));
M.getOrInsertNamedMetadata("dx.source.args")
->addOperand(llvm::MDNode::get(Ctx, Args));
}
// Find array variable declaration from DeclRef expression
static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) {
E = E->IgnoreImpCasts();
if (const auto *DRE = dyn_cast_or_null<DeclRefExpr>(E))
return DRE->getDecl();
if (auto *OVE = dyn_cast<OpaqueValueExpr>(E))
E = OVE->getSourceExpr()->IgnoreImpCasts();
if (isa<MemberExpr>(E))
return findAssociatedResourceDeclForStruct(AST, cast<MemberExpr>(E));
return nullptr;
}
// Find array variable declaration from nested array subscript AST nodes
static const ValueDecl *getArrayDecl(ASTContext &AST,
const ArraySubscriptExpr *ASE) {
const Expr *E = nullptr;
while (ASE != nullptr) {
E = ASE->getBase()->IgnoreImpCasts();
if (!E)
return nullptr;
ASE = dyn_cast<ArraySubscriptExpr>(E);
}
return getArrayDecl(AST, E);
}
// Get the total size of the array, or 0 if the array is unbounded.
static int getTotalArraySize(ASTContext &AST, const clang::Type *Ty) {
Ty = Ty->getUnqualifiedDesugaredType();
assert(Ty->isArrayType() && "expected array type");
if (Ty->isIncompleteArrayType())
return 0;
return AST.getConstantArrayElementCount(cast<ConstantArrayType>(Ty));
}
static Value *buildNameForResource(llvm::StringRef BaseName,
CodeGenModule &CGM) {
llvm::SmallString<64> GlobalName = {BaseName, ".str"};
return CGM.GetAddrOfConstantCString(BaseName.str(), GlobalName.c_str())
.getPointer();
}
static CXXMethodDecl *lookupMethod(CXXRecordDecl *Record, StringRef Name,
StorageClass SC = SC_None) {
for (auto *Method : Record->methods()) {
if (Method->getStorageClass() == SC && Method->getName() == Name)
return Method;
}
return nullptr;
}
static CXXMethodDecl *lookupResourceInitMethodAndSetupArgs(
CodeGenModule &CGM, CXXRecordDecl *ResourceDecl, llvm::Value *Range,
llvm::Value *Index, StringRef Name, ResourceBindingAttrs &Binding,
CallArgList &Args) {
assert(Binding.hasBinding() && "at least one binding attribute expected");
ASTContext &AST = CGM.getContext();
CXXMethodDecl *CreateMethod = nullptr;
Value *NameStr = buildNameForResource(Name, CGM);
Value *Space = llvm::ConstantInt::get(CGM.IntTy, Binding.getSpace());
bool HasCounter = hasCounterHandle(ResourceDecl);
assert((!HasCounter || Binding.hasCounterImplicitOrderID()) &&
"resources with counter handle must have a binding with counter "
"implicit order ID");
if (Binding.isExplicit()) {
// explicit binding
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, Binding.getSlot());
Args.add(RValue::get(RegSlot), AST.UnsignedIntTy);
const char *Name = Binding.hasCounterImplicitOrderID()
? "__createFromBindingWithImplicitCounter"
: "__createFromBinding";
CreateMethod = lookupMethod(ResourceDecl, Name, SC_Static);
} else {
// implicit binding
auto *OrderID =
llvm::ConstantInt::get(CGM.IntTy, Binding.getImplicitOrderID());
Args.add(RValue::get(OrderID), AST.UnsignedIntTy);
const char *Name = Binding.hasCounterImplicitOrderID()
? "__createFromImplicitBindingWithImplicitCounter"
: "__createFromImplicitBinding";
CreateMethod = lookupMethod(ResourceDecl, Name, SC_Static);
}
Args.add(RValue::get(Space), AST.UnsignedIntTy);
Args.add(RValue::get(Range), AST.IntTy);
Args.add(RValue::get(Index), AST.UnsignedIntTy);
Args.add(RValue::get(NameStr), AST.getPointerType(AST.CharTy.withConst()));
if (HasCounter) {
uint32_t CounterBinding = Binding.getCounterImplicitOrderID();
auto *CounterOrderID = llvm::ConstantInt::get(CGM.IntTy, CounterBinding);
Args.add(RValue::get(CounterOrderID), AST.UnsignedIntTy);
}
return CreateMethod;
}
static void callResourceInitMethod(CodeGenFunction &CGF,
CXXMethodDecl *CreateMethod,
CallArgList &Args, Address ReturnAddress) {
llvm::Constant *CalleeFn = CGF.CGM.GetAddrOfFunction(CreateMethod);
const FunctionProtoType *Proto =
CreateMethod->getType()->getAs<FunctionProtoType>();
// HLSL code generation is restricted to DXIL and SPIR-V targets, so no
// caller declaration is needed for x86 SysV ABI selection.
const CGFunctionInfo &FnInfo = CGF.CGM.getTypes().arrangeFreeFunctionCall(
Args, Proto, false, /*ABIInfoFD=*/nullptr);
ReturnValueSlot ReturnValue(ReturnAddress, false);
CGCallee Callee(CGCalleeInfo(Proto), CalleeFn);
CGF.EmitCall(FnInfo, Callee, ReturnValue, Args, nullptr);
}
// Initializes local resource array variable with global resource array
// elements. For multi-dimensional arrays it calls itself recursively to
// initialize its sub-arrays. The Index used in the resource constructor calls
// will begin at StartIndex and will be incremented for each array element. The
// last used resource Index is returned to the caller. If the function returns
// std::nullopt, it indicates an error.
static std::optional<llvm::Value *> initializeResourceArrayFromGlobal(
CodeGenFunction &CGF, CXXRecordDecl *ResourceDecl,
const ConstantArrayType *ArrayTy, AggValueSlot &ValueSlot,
llvm::Value *Range, llvm::Value *StartIndex, StringRef ResourceName,
ResourceBindingAttrs &Binding, ArrayRef<llvm::Value *> PrevGEPIndices) {
ASTContext &AST = CGF.getContext();
llvm::IntegerType *IntTy = CGF.CGM.IntTy;
llvm::Value *Index = StartIndex;
llvm::Value *One = llvm::ConstantInt::get(IntTy, 1);
const uint64_t ArraySize = ArrayTy->getSExtSize();
QualType ElemType = ArrayTy->getElementType();
Address TmpArrayAddr = ValueSlot.getAddress();
// Add additional index to the getelementptr call indices.
// This index will be updated for each array element in the loops below.
SmallVector<llvm::Value *> GEPIndices(PrevGEPIndices);
GEPIndices.push_back(llvm::ConstantInt::get(IntTy, 0));
// For array of arrays, recursively initialize the sub-arrays.
if (ElemType->isArrayType()) {
const ConstantArrayType *SubArrayTy = cast<ConstantArrayType>(ElemType);
for (uint64_t I = 0; I < ArraySize; I++) {
if (I > 0) {
Index = CGF.Builder.CreateAdd(Index, One);
GEPIndices.back() = llvm::ConstantInt::get(IntTy, I);
}
std::optional<llvm::Value *> MaybeIndex =
initializeResourceArrayFromGlobal(CGF, ResourceDecl, SubArrayTy,
ValueSlot, Range, Index,
ResourceName, Binding, GEPIndices);
if (!MaybeIndex)
return std::nullopt;
Index = *MaybeIndex;
}
return Index;
}
// For array of resources, initialize each resource in the array.
llvm::Type *Ty = CGF.ConvertTypeForMem(ElemType);
CharUnits ElemSize = AST.getTypeSizeInChars(ElemType);
CharUnits Align =
TmpArrayAddr.getAlignment().alignmentOfArrayElement(ElemSize);
for (uint64_t I = 0; I < ArraySize; I++) {
if (I > 0) {
Index = CGF.Builder.CreateAdd(Index, One);
GEPIndices.back() = llvm::ConstantInt::get(IntTy, I);
}
Address ReturnAddress =
CGF.Builder.CreateGEP(TmpArrayAddr, GEPIndices, Ty, Align);
CallArgList Args;
CXXMethodDecl *CreateMethod = lookupResourceInitMethodAndSetupArgs(
CGF.CGM, ResourceDecl, Range, Index, ResourceName, Binding, Args);
if (!CreateMethod)
// This can happen if someone creates an array of structs that looks like
// an HLSL resource record array but it does not have the required static
// create method. No binding will be generated for it.
return std::nullopt;
callResourceInitMethod(CGF, CreateMethod, Args, ReturnAddress);
}
return Index;
}
/// Utility for emitting copies following the HLSL buffer layout rules (ie,
/// copying out of a cbuffer).
class HLSLBufferCopyEmitter {
CodeGenFunction &CGF;
Address DstPtr;
Address SrcPtr;
llvm::Type *LayoutTy = nullptr;
SmallVector<llvm::Value *> CurStoreIndices;
SmallVector<llvm::Value *> CurLoadIndices;
using EmitResourceFnTy = llvm::function_ref<void(AggValueSlot &)>;
// Creates & returns either a structured.gep or a ptradd/gep depending on
// langopts.
llvm::Value *emitAccessChain(llvm::Type *BaseTy, llvm::Value *Base,
ArrayRef<llvm::Value *> Indices) {
bool EmitLogical = CGF.getLangOpts().EmitLogicalPointer;
if (EmitLogical)
return CGF.Builder.CreateAccessChain(EmitLogical, BaseTy, Base, Indices);
llvm::SmallVector<llvm::Value *> GEPIndices;
GEPIndices.reserve(Indices.size() + 1);
GEPIndices.push_back(llvm::ConstantInt::get(CGF.IntTy, 0));
GEPIndices.append(Indices.begin(), Indices.end());
return CGF.Builder.CreateAccessChain(EmitLogical, BaseTy, Base, GEPIndices);
}
bool isBufferLayoutArray(llvm::StructType *ST) {
// A buffer layout array is a struct with two elements: the padded array,
// and the last element. That is, is should look something like this:
//
// { [%n x { %type, %padding }], %type }
//
if (!ST || ST->getNumElements() != 2)
return false;
auto *PaddedEltsTy = dyn_cast<llvm::ArrayType>(ST->getElementType(0));
if (!PaddedEltsTy)
return false;
auto *PaddedTy = dyn_cast<llvm::StructType>(PaddedEltsTy->getElementType());
if (!PaddedTy || PaddedTy->getNumElements() != 2)
return false;
if (!CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(
PaddedTy->getElementType(1)))
return false;
llvm::Type *ElementTy = ST->getElementType(1);
if (PaddedTy->getElementType(0) != ElementTy)
return false;
return true;
}
// Returns true if the type is either a struct representing a resource record,
// or an array of structs that are resource records. This assumes a struct is
// a resource record if the first element is a target type (resource handle).
// This is the case for all target types used by HLSL except the padding type
// ("{dx|spirv.Padding"), but padding will never be the first element of a
// struct.
bool isResourceOrResourceArray(llvm::Type *Ty) {
while (auto *AT = dyn_cast<llvm::ArrayType>(Ty))
Ty = AT->getElementType();
auto *ST = dyn_cast<llvm::StructType>(Ty);
if (!ST || ST->getNumElements() < 1)
return false;
auto *TargetTy = dyn_cast<llvm::TargetExtType>(ST->getElementType(0));
return TargetTy != nullptr;
}
void emitResourceOrResourceArray(Value *Dst, llvm::Type *DstTy,
EmitResourceFnTy EmitResFn) {
CharUnits DstAlign =
CharUnits::fromQuantity(CGF.CGM.getDataLayout().getABITypeAlign(DstTy));
Address DstAddr(Dst, DstTy, DstAlign);
AggValueSlot Slot = AggValueSlot::forAddr(
DstAddr, Qualifiers(), AggValueSlot::IsDestructed_t(true),
AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
AggValueSlot::DoesNotOverlap);
EmitResFn(Slot);
}
void emitBufferLayoutCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
llvm::ArrayType *DstTy,
EmitResourceFnTy EmitResFn) {
// Those assumptions are checked by isBufferLayoutArray.
auto *SrcPaddedArrayTy = cast<llvm::ArrayType>(SrcTy->getElementType(0));
assert(SrcPaddedArrayTy->getNumElements() + 1 == DstTy->getNumElements());
assert(cast<llvm::StructType>(SrcPaddedArrayTy->getElementType())
->getElementType(0) == SrcTy->getElementType(1));
auto *SrcDataTy = SrcTy->getElementType(1);
auto Zero = llvm::ConstantInt::get(CGF.IntTy, 0);
for (unsigned I = 0; I < SrcPaddedArrayTy->getNumElements(); ++I) {
auto Index = llvm::ConstantInt::get(CGF.IntTy, I);
auto *SrcElt = emitAccessChain(SrcTy, Src, {Zero, Index, Zero});
auto *DstElt = emitAccessChain(DstTy, Dst, {Index});
emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType(),
EmitResFn);
}
auto *SrcElt =
emitAccessChain(SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, 1)});
auto *DstElt = emitAccessChain(
DstTy, Dst,
{llvm::ConstantInt::get(CGF.IntTy, DstTy->getNumElements() - 1)});
emitElementCopy(SrcElt, SrcDataTy, DstElt, DstTy->getElementType(),
EmitResFn);
}
void emitCopy(Value *Src, llvm::StructType *SrcTy, Value *Dst,
llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
assert(!isResourceOrResourceArray(DstTy) &&
"direct access to resources or resource arrays should be handled "
"separately");
if (isBufferLayoutArray(SrcTy))
return emitBufferLayoutCopy(Src, SrcTy, Dst, cast<llvm::ArrayType>(DstTy),
EmitResFn);
unsigned SrcIndex = 0;
unsigned DstIndex = 0;
// DstTy layout is in default address space and can include resource types.
// SrcTy is in cbuffer layout where resources are filtered out, so the
// number of elements in SrcTy can be less than the number of elements in
// DstTy.
auto *DstST = cast<llvm::StructType>(DstTy);
while (DstIndex < DstST->getNumElements()) {
llvm::Type *DstEltTy = DstST->getElementType(DstIndex);
if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(DstEltTy)) {
DstIndex += 1;
continue;
}
if (isResourceOrResourceArray(DstEltTy)) {
auto *DstElt = emitAccessChain(
DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, DstIndex)});
emitResourceOrResourceArray(DstElt, DstEltTy, EmitResFn);
DstIndex += 1;
continue;
}
assert(SrcIndex < SrcTy->getNumElements());
llvm::Type *SrcEltTy = SrcTy->getElementType(SrcIndex);
if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(SrcEltTy)) {
SrcIndex += 1;
continue;
}
auto *SrcElt = emitAccessChain(
SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, SrcIndex)});
auto *DstElt = emitAccessChain(
DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, DstIndex)});
emitElementCopy(SrcElt, SrcEltTy, DstElt, DstEltTy, EmitResFn);
DstIndex += 1;
SrcIndex += 1;
}
}
void emitCopy(Value *Src, llvm::ArrayType *SrcTy, Value *Dst,
llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
for (unsigned I = 0, E = SrcTy->getNumElements(); I < E; ++I) {
auto *SrcElt =
emitAccessChain(SrcTy, Src, {llvm::ConstantInt::get(CGF.IntTy, I)});
auto *DstElt =
emitAccessChain(DstTy, Dst, {llvm::ConstantInt::get(CGF.IntTy, I)});
emitElementCopy(SrcElt, SrcTy->getElementType(), DstElt,
cast<llvm::ArrayType>(DstTy)->getElementType(),
EmitResFn);
}
}
void emitElementCopy(Value *Src, llvm::Type *SrcTy, Value *Dst,
llvm::Type *DstTy, EmitResourceFnTy EmitResFn) {
if (auto *AT = dyn_cast<llvm::ArrayType>(SrcTy))
return emitCopy(Src, AT, Dst, DstTy, EmitResFn);
if (auto *ST = dyn_cast<llvm::StructType>(SrcTy))
return emitCopy(Src, ST, Dst, DstTy, EmitResFn);
// When we have a scalar or vector element we can emit the copy.
CharUnits SrcAlign =
CharUnits::fromQuantity(CGF.CGM.getDataLayout().getABITypeAlign(SrcTy));
CharUnits DstAlign =
CharUnits::fromQuantity(CGF.CGM.getDataLayout().getABITypeAlign(DstTy));
Address SrcAddr(Src, SrcTy, SrcAlign);
Address DstAddr(Dst, DstTy, DstAlign);
llvm::Value *Load = CGF.Builder.CreateLoad(SrcAddr, "cbuf.load");
CGF.Builder.CreateStore(Load, DstAddr);
}
public:
HLSLBufferCopyEmitter(CodeGenFunction &CGF, Address DstPtr, Address SrcPtr)
: CGF(CGF), DstPtr(DstPtr), SrcPtr(SrcPtr) {}
bool emitCopy(QualType CType, EmitResourceFnTy EmitResFn = nullptr) {
LayoutTy = HLSLBufferLayoutBuilder(CGF.CGM).layOutType(CType);
// TODO: We should be able to fall back to a regular memcpy if the layout
// type doesn't have any padding, but that runs into issues in the backend
// currently.
//
// See https://github.com/llvm/wg-hlsl/issues/351
emitElementCopy(SrcPtr.getBasePointer(), LayoutTy, DstPtr.getBasePointer(),
DstPtr.getElementType(), EmitResFn);
return true;
}
};
// Represents a list resources associated with a global struct whose name
// starts with the specified prefix.
// The order of HLSLAssociatedResourceDeclAttr attributes is identical to the
// order of the depth-first traversal of the corresponding fields in the struct.
// The resources are always returned in that order, which is the same order
// we need when a struct is copied element-by-element.
class AssociatedResourcesList {
// Iterator pointers for the associated resource attributes that match the
// prefix. Begin = begin of the range of attributes that match the prefix End
// = end of the range of attributes that match the prefix Next = the current
// attribute in the iteration to be returned by getNextResource
specific_attr_iterator<HLSLAssociatedResourceDeclAttr> Begin, End, Next;
public:
AssociatedResourcesList(const VarDecl *StructVD,
StringRef ResourceNamePrefix) {
auto I = StructVD->specific_attr_begin<HLSLAssociatedResourceDeclAttr>();
auto E = StructVD->specific_attr_end<HLSLAssociatedResourceDeclAttr>();
// Skip over associated resources that don't match the prefix.
while (I != E &&
!I->getResDecl()->getName().starts_with(ResourceNamePrefix))
++I;
assert(I != E && "expected associated resource not found");
Begin = End = I;
// Scan over associated resources that do match the prefix to find the end
// of the range.
while (I != E && ((HLSLAssociatedResourceDeclAttr *)*I)
->getResDecl()
->getName()
.starts_with(ResourceNamePrefix))
End = ++I;
Next = Begin;
}
const VarDecl *getNextResource() {
if (Next == End)
return nullptr;
const VarDecl *Res = Next->getResDecl();
++Next;
return Res;
}
};
} // namespace
llvm::Type *
CGHLSLRuntime::convertHLSLSpecificType(const Type *T,
const CGHLSLOffsetInfo &OffsetInfo) {
assert(T->isHLSLSpecificType() && "Not an HLSL specific type!");
// Check if the target has a specific translation for this type first.
if (llvm::Type *TargetTy =
CGM.getTargetCodeGenInfo().getHLSLType(CGM, T, OffsetInfo))
return TargetTy;
llvm_unreachable("Generic handling of HLSL types is not supported.");
}
llvm::Triple::ArchType CGHLSLRuntime::getArch() {
return CGM.getTarget().getTriple().getArch();
}
// Emits constant global variables for buffer constants declarations
// and creates metadata linking the constant globals with the buffer global.
void CGHLSLRuntime::emitBufferGlobalsAndMetadata(
const HLSLBufferDecl *BufDecl, llvm::GlobalVariable *BufGV,
const CGHLSLOffsetInfo &OffsetInfo) {
LLVMContext &Ctx = CGM.getLLVMContext();
// get the layout struct from constant buffer target type
llvm::Type *BufType = BufGV->getValueType();
llvm::StructType *LayoutStruct = cast<llvm::StructType>(
cast<llvm::TargetExtType>(BufType)->getTypeParameter(0));
SmallVector<std::pair<VarDecl *, uint32_t>> DeclsWithOffset;
size_t OffsetIdx = 0;
for (Decl *D : BufDecl->buffer_decls()) {
if (isa<CXXRecordDecl, EmptyDecl>(D))
// Nothing to do for this declaration.
continue;
if (isa<FunctionDecl>(D)) {
// A function within an cbuffer is effectively a top-level function.
CGM.EmitTopLevelDecl(D);
continue;
}
VarDecl *VD = dyn_cast<VarDecl>(D);
if (!VD)
continue;
QualType VDTy = VD->getType();
if (VDTy.getAddressSpace() != LangAS::hlsl_constant) {
if (VD->getStorageClass() == SC_Static ||
VDTy.getAddressSpace() == LangAS::hlsl_groupshared ||
VDTy->isHLSLResourceRecord() || VDTy->isHLSLResourceRecordArray()) {
// Emit static and groupshared variables and resource classes inside
// cbuffer as regular globals
CGM.EmitGlobal(VD);
}
continue;
}
DeclsWithOffset.emplace_back(VD, OffsetInfo[OffsetIdx++]);
}
if (!OffsetInfo.empty())
llvm::stable_sort(DeclsWithOffset, [](const auto &LHS, const auto &RHS) {
return CGHLSLOffsetInfo::compareOffsets(LHS.second, RHS.second);
});
// Associate the buffer global variable with its constants
SmallVector<llvm::Metadata *> BufGlobals;
BufGlobals.reserve(DeclsWithOffset.size() + 1);
BufGlobals.push_back(ValueAsMetadata::get(BufGV));
auto ElemIt = LayoutStruct->element_begin();
for (auto &[VD, _] : DeclsWithOffset) {
if (CGM.getTargetCodeGenInfo().isHLSLPadding(*ElemIt))
++ElemIt;
assert(ElemIt != LayoutStruct->element_end() &&
"number of elements in layout struct does not match");
llvm::Type *LayoutType = *ElemIt++;
GlobalVariable *ElemGV =
cast<GlobalVariable>(CGM.GetAddrOfGlobalVar(VD, LayoutType));
BufGlobals.push_back(ValueAsMetadata::get(ElemGV));
}
assert(ElemIt == LayoutStruct->element_end() &&
"number of elements in layout struct does not match");
// add buffer metadata to the module
CGM.getModule()
.getOrInsertNamedMetadata("hlsl.cbs")
->addOperand(MDNode::get(Ctx, BufGlobals));
}
// Creates resource handle type for the HLSL buffer declaration
static const clang::HLSLAttributedResourceType *
createBufferHandleType(const HLSLBufferDecl *BufDecl) {
ASTContext &AST = BufDecl->getASTContext();
QualType QT = AST.getHLSLAttributedResourceType(
AST.HLSLResourceTy, AST.getCanonicalTagType(BufDecl->getLayoutStruct()),
HLSLAttributedResourceType::Attributes(ResourceClass::CBuffer));
return cast<HLSLAttributedResourceType>(QT.getTypePtr());
}
CGHLSLOffsetInfo CGHLSLOffsetInfo::fromDecl(const HLSLBufferDecl &BufDecl) {
CGHLSLOffsetInfo Result;
// If we don't have packoffset info, just return an empty result.
if (!BufDecl.hasValidPackoffset())
return Result;
for (Decl *D : BufDecl.buffer_decls()) {
if (isa<CXXRecordDecl, EmptyDecl>(D) || isa<FunctionDecl>(D)) {
continue;
}
VarDecl *VD = dyn_cast<VarDecl>(D);
if (!VD || VD->getType().getAddressSpace() != LangAS::hlsl_constant)
continue;
if (!VD->hasAttrs()) {
Result.Offsets.push_back(Unspecified);
continue;
}
uint32_t Offset = Unspecified;
for (auto *Attr : VD->getAttrs()) {
if (auto *POA = dyn_cast<HLSLPackOffsetAttr>(Attr)) {
Offset = POA->getOffsetInBytes();
break;
}
auto *RBA = dyn_cast<HLSLResourceBindingAttr>(Attr);
if (RBA &&
RBA->getRegisterType() == HLSLResourceBindingAttr::RegisterType::C) {
Offset = RBA->getSlotNumber() * CBufferRowSizeInBytes;
break;
}
}
Result.Offsets.push_back(Offset);
}
return Result;
}
// Codegen for HLSLBufferDecl
void CGHLSLRuntime::addBuffer(const HLSLBufferDecl *BufDecl) {
assert(BufDecl->isCBuffer() && "tbuffer codegen is not supported yet");
// create resource handle type for the buffer
const clang::HLSLAttributedResourceType *ResHandleTy =
createBufferHandleType(BufDecl);
// empty constant buffer is ignored
if (ResHandleTy->getContainedType()->getAsCXXRecordDecl()->isEmpty())
return;
// create global variable for the constant buffer
CGHLSLOffsetInfo OffsetInfo = CGHLSLOffsetInfo::fromDecl(*BufDecl);
llvm::Type *LayoutTy = convertHLSLSpecificType(ResHandleTy, OffsetInfo);
llvm::GlobalVariable *BufGV = new GlobalVariable(
LayoutTy, /*isConstant*/ false,
GlobalValue::LinkageTypes::InternalLinkage, PoisonValue::get(LayoutTy),
llvm::formatv("{0}{1}", BufDecl->getName(),
BufDecl->isCBuffer() ? ".cb" : ".tb"),
GlobalValue::NotThreadLocal);
llvm::Module &M = CGM.getModule();
M.insertGlobalVariable(BufGV);
// Add the global variable to the compiler used list so it does not
// get optimized away by GlobalOptPass before it reaches
// {DXIL|SPIRV}CBufferAccess pass.
llvm::appendToCompilerUsed(M, {BufGV});
// Add globals for constant buffer elements and create metadata nodes
emitBufferGlobalsAndMetadata(BufDecl, BufGV, OffsetInfo);
// Initialize cbuffer from binding (implicit or explicit)
initializeBufferFromBinding(BufDecl, BufGV);
}
void CGHLSLRuntime::addRootSignature(
const HLSLRootSignatureDecl *SignatureDecl) {
llvm::Module &M = CGM.getModule();
Triple T(M.getTargetTriple());
// Generated later with the function decl if not targeting root signature
if (T.getEnvironment() != Triple::EnvironmentType::RootSignature)
return;
addRootSignatureMD(SignatureDecl->getVersion(),
SignatureDecl->getRootElements(), nullptr, M);
}
llvm::StructType *
CGHLSLRuntime::getHLSLBufferLayoutType(const RecordType *StructType) {
const auto Entry = LayoutTypes.find(StructType);
if (Entry != LayoutTypes.end())
return Entry->getSecond();
return nullptr;
}
void CGHLSLRuntime::addHLSLBufferLayoutType(const RecordType *StructType,
llvm::StructType *LayoutTy) {
assert(getHLSLBufferLayoutType(StructType) == nullptr &&
"layout type for this struct already exist");
LayoutTypes[StructType] = LayoutTy;
}
void CGHLSLRuntime::finishCodeGen() {
auto &TargetOpts = CGM.getTarget().getTargetOpts();
auto &CodeGenOpts = CGM.getCodeGenOpts();
auto &LangOpts = CGM.getLangOpts();
llvm::Module &M = CGM.getModule();
Triple T(M.getTargetTriple());
if (T.getArch() == Triple::ArchType::dxil)
addDxilValVersion(TargetOpts.DxilValidatorVersion, M);
if (!CodeGenOpts.DisableDXSourceMetadata &&
CodeGenOpts.getDebugInfo() >=
llvm::codegenoptions::DebugInfoKind::DebugInfoConstructor)
addSourceInfo(CGM, M);
if (CodeGenOpts.ResMayAlias)
M.setModuleFlag(llvm::Module::ModFlagBehavior::Error, "dx.resmayalias", 1);
if (CodeGenOpts.AllResourcesBound)
M.setModuleFlag(llvm::Module::ModFlagBehavior::Error,
"dx.allresourcesbound", 1);
if (CodeGenOpts.OptimizationLevel == 0)
M.addModuleFlag(llvm::Module::ModFlagBehavior::Override,
"dx.disable_optimizations", 1);
// NativeHalfType corresponds to the -fnative-half-type clang option which is
// aliased by clang-dxc's -enable-16bit-types option. This option is used to
// set the UseNativeLowPrecision DXIL module flag in the DirectX backend
if (LangOpts.NativeHalfType)
M.setModuleFlag(llvm::Module::ModFlagBehavior::Error, "dx.nativelowprec",
1);
if (LangOpts.HLSLSpvPreserveInterface && T.isSPIRV()) {
// Runs before optimization. Keeps Input/Output globals from GlobalDCE.
const ASTContext &Ctx = CGM.getContext();
unsigned InputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_input);
unsigned OutputAS = Ctx.getTargetAddressSpace(LangAS::hlsl_output);
SmallVector<GlobalValue *, 8> InterfaceVars;
for (GlobalVariable &GV : M.globals()) {
unsigned AS = GV.getAddressSpace();
if (AS == InputAS || AS == OutputAS)
InterfaceVars.push_back(&GV);
}
if (!InterfaceVars.empty())
appendToCompilerUsed(M, InterfaceVars);
}
generateGlobalCtorDtorCalls();
}
void clang::CodeGen::CGHLSLRuntime::setHLSLEntryAttributes(
const FunctionDecl *FD, llvm::Function *Fn) {
const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
assert(ShaderAttr && "All entry functions must have a HLSLShaderAttr");
const StringRef ShaderAttrKindStr = "hlsl.shader";
Fn->addFnAttr(ShaderAttrKindStr,
llvm::Triple::getEnvironmentTypeName(ShaderAttr->getType()));
if (HLSLNumThreadsAttr *NumThreadsAttr = FD->getAttr<HLSLNumThreadsAttr>()) {
const StringRef NumThreadsKindStr = "hlsl.numthreads";
std::string NumThreadsStr =
formatv("{0},{1},{2}", NumThreadsAttr->getX(), NumThreadsAttr->getY(),
NumThreadsAttr->getZ());
Fn->addFnAttr(NumThreadsKindStr, NumThreadsStr);
}
if (HLSLWaveSizeAttr *WaveSizeAttr = FD->getAttr<HLSLWaveSizeAttr>()) {
const StringRef WaveSizeKindStr = "hlsl.wavesize";
std::string WaveSizeStr =
formatv("{0},{1},{2}", WaveSizeAttr->getMin(), WaveSizeAttr->getMax(),
WaveSizeAttr->getPreferred());
Fn->addFnAttr(WaveSizeKindStr, WaveSizeStr);
}
// HLSL entry functions are materialized for module functions with
// HLSLShaderAttr attribute. SetLLVMFunctionAttributesForDefinition called
// later in the compiler-flow for such module functions is not aware of and
// hence not able to set attributes of the newly materialized entry functions.
// So, set attributes of entry function here, as appropriate.
Fn->addFnAttr(llvm::Attribute::NoInline);
if (CGM.getLangOpts().HLSLSpvEnableMaximalReconvergence) {
Fn->addFnAttr("enable-maximal-reconvergence", "true");
}
}
static Value *buildVectorInput(IRBuilder<> &B, Function *F, llvm::Type *Ty) {
if (const auto *VT = dyn_cast<FixedVectorType>(Ty)) {
Value *Result = PoisonValue::get(Ty);
for (unsigned I = 0; I < VT->getNumElements(); ++I) {
Value *Elt = B.CreateCall(F, {B.getInt32(I)});
Result = B.CreateInsertElement(Result, Elt, I);
}
return Result;
}
return B.CreateCall(F, {B.getInt32(0)});
}
static void addSPIRVBuiltinDecoration(llvm::GlobalVariable *GV,
unsigned BuiltIn) {
LLVMContext &Ctx = GV->getContext();
IRBuilder<> B(GV->getContext());
MDNode *Operands = MDNode::get(
Ctx,
{ConstantAsMetadata::get(B.getInt32(/* Spirv::Decoration::BuiltIn */ 11)),
ConstantAsMetadata::get(B.getInt32(BuiltIn))});
MDNode *Decoration = MDNode::get(Ctx, {Operands});
GV->addMetadata("spirv.Decorations", *Decoration);
}
static void addLocationDecoration(llvm::GlobalVariable *GV, unsigned Location) {
LLVMContext &Ctx = GV->getContext();
IRBuilder<> B(GV->getContext());
MDNode *Operands =
MDNode::get(Ctx, {ConstantAsMetadata::get(B.getInt32(/* Location */ 30)),
ConstantAsMetadata::get(B.getInt32(Location))});
MDNode *Decoration = MDNode::get(Ctx, {Operands});
GV->addMetadata("spirv.Decorations", *Decoration);
}
// A fragment shader input interface variable whose base type is an integer or
// a 64-bit float (double) cannot be interpolated by the rasterizer. The Vulkan
// specification requires these variables to be decorated with Flat (see
// VUID-StandaloneSpirv-Flat-04744). Arrays and vectors are unwrapped to inspect
// their base scalar type.
static bool inputRequiresFlatDecoration(llvm::Type *Ty) {
while (true) {
if (auto *AT = dyn_cast<llvm::ArrayType>(Ty)) {
Ty = AT->getElementType();
continue;
}
if (auto *VT = dyn_cast<llvm::FixedVectorType>(Ty)) {
Ty = VT->getElementType();
continue;
}
break;
}
return Ty->isIntegerTy() || Ty->isDoubleTy();
}
static llvm::Value *createSPIRVBuiltinLoad(IRBuilder<> &B, llvm::Module &M,
llvm::Type *Ty, const Twine &Name,
unsigned BuiltInID) {
auto *GV = new llvm::GlobalVariable(
M, Ty, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage,
/* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr,
llvm::GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 7, /* isExternallyInitialized= */ true);
addSPIRVBuiltinDecoration(GV, BuiltInID);
GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
return B.CreateLoad(Ty, GV);
}
static llvm::Value *createSPIRVLocationLoad(IRBuilder<> &B, llvm::Module &M,
llvm::Type *Ty, unsigned Location,
StringRef Name, bool NeedsFlat) {
auto *GV = new llvm::GlobalVariable(
M, Ty, /* isConstant= */ true, llvm::GlobalValue::ExternalLinkage,
/* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr,
llvm::GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 7, /* isExternallyInitialized= */ true);
GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
// Emit all decorations as a single `spirv.Decorations` node. Attaching
// multiple `spirv.Decorations` metadata nodes to the same global is not
// supported by the SPIR-V backend and results in all but one being dropped.
LLVMContext &Ctx = GV->getContext();
SmallVector<Metadata *, 2> Decorations;
Decorations.push_back(
MDNode::get(Ctx, {ConstantAsMetadata::get(
B.getInt32(/* SPIRV::Decoration::Location */ 30)),
ConstantAsMetadata::get(B.getInt32(Location))}));
if (NeedsFlat)
Decorations.push_back(
MDNode::get(Ctx, {ConstantAsMetadata::get(
B.getInt32(/* SPIRV::Decoration::Flat */ 14))}));
GV->addMetadata("spirv.Decorations", *MDNode::get(Ctx, Decorations));
return B.CreateLoad(Ty, GV);
}
llvm::Value *CGHLSLRuntime::emitSPIRVUserSemanticLoad(
llvm::IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
Twine BaseName = Twine(Semantic->getAttrName()->getName());
Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
unsigned Location = SPIRVLastAssignedInputSemanticLocation;
if (auto *L = Decl->getAttr<HLSLVkLocationAttr>())
Location = L->getLocation();
// DXC completely ignores the semantic/index pair. Location are assigned from
// the first semantic to the last.
llvm::ArrayType *AT = dyn_cast<llvm::ArrayType>(Type);
unsigned ElementCount = AT ? AT->getNumElements() : 1;
SPIRVLastAssignedInputSemanticLocation += ElementCount;
const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
bool NeedsFlat =
ShaderAttr &&
ShaderAttr->getType() == llvm::Triple::EnvironmentType::Pixel &&
inputRequiresFlatDecoration(Type);
return createSPIRVLocationLoad(B, CGM.getModule(), Type, Location,
VariableName.str(), NeedsFlat);
}
static void createSPIRVLocationStore(IRBuilder<> &B, llvm::Module &M,
llvm::Value *Source, unsigned Location,
StringRef Name) {
auto *GV = new llvm::GlobalVariable(
M, Source->getType(), /* isConstant= */ false,
llvm::GlobalValue::ExternalLinkage,
/* Initializer= */ nullptr, /* Name= */ Name, /* insertBefore= */ nullptr,
llvm::GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 8, /* isExternallyInitialized= */ false);
GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
addLocationDecoration(GV, Location);
B.CreateStore(Source, GV);
}
void CGHLSLRuntime::emitSPIRVUserSemanticStore(
llvm::IRBuilder<> &B, llvm::Value *Source,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
Twine BaseName = Twine(Semantic->getAttrName()->getName());
Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
unsigned Location = SPIRVLastAssignedOutputSemanticLocation;
if (auto *L = Decl->getAttr<HLSLVkLocationAttr>())
Location = L->getLocation();
// DXC completely ignores the semantic/index pair. Location are assigned from
// the first semantic to the last.
llvm::ArrayType *AT = dyn_cast<llvm::ArrayType>(Source->getType());
unsigned ElementCount = AT ? AT->getNumElements() : 1;
SPIRVLastAssignedOutputSemanticLocation += ElementCount;
createSPIRVLocationStore(B, CGM.getModule(), Source, Location,
VariableName.str());
}
llvm::Value *
CGHLSLRuntime::emitDXILUserSemanticLoad(llvm::IRBuilder<> &B, llvm::Type *Type,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
Twine BaseName = Twine(Semantic->getAttrName()->getName());
Twine VariableName = BaseName.concat(Twine(Index.value_or(0)));
// DXIL packing rules etc shall be handled here.
// FIXME: generate proper sigpoint, index, col, row values.
// FIXME: also DXIL loads vectors element by element.
SmallVector<Value *> Args{B.getInt32(4), B.getInt32(0), B.getInt32(0),
B.getInt8(0),
llvm::PoisonValue::get(B.getInt32Ty())};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_load_input;
SmallVector<OperandBundleDef, 1> OB;
if (auto *Token = getConvergenceToken(*B.GetInsertBlock())) {
llvm::Value *bundleArgs[] = {Token};
OB.emplace_back("convergencectrl", bundleArgs);
}
llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
B.GetInsertBlock()->getModule(), IntrinsicID, {Type});
llvm::Value *Value = B.CreateCall(IntrFn, Args, OB, VariableName);
return Value;
}
void CGHLSLRuntime::emitDXILUserSemanticStore(llvm::IRBuilder<> &B,
llvm::Value *Source,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
// DXIL packing rules etc shall be handled here.
// FIXME: generate proper sigpoint, index, col, row values.
SmallVector<Value *> Args{B.getInt32(4),
B.getInt32(0),
B.getInt32(0),
B.getInt8(0),
llvm::PoisonValue::get(B.getInt32Ty()),
Source};
llvm::Intrinsic::ID IntrinsicID = llvm::Intrinsic::dx_store_output;
SmallVector<OperandBundleDef, 1> OB;
if (auto *Token = getConvergenceToken(*B.GetInsertBlock())) {
llvm::Value *bundleArgs[] = {Token};
OB.emplace_back("convergencectrl", bundleArgs);
}
llvm::Function *IntrFn = llvm::Intrinsic::getOrInsertDeclaration(
B.GetInsertBlock()->getModule(), IntrinsicID, {Source->getType()});
B.CreateCall(IntrFn, Args, OB);
}
llvm::Value *CGHLSLRuntime::emitUserSemanticLoad(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
if (CGM.getTarget().getTriple().isSPIRV())
return emitSPIRVUserSemanticLoad(B, FD, Type, Decl, Semantic, Index);
if (CGM.getTarget().getTriple().isDXIL())
return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
llvm_unreachable("Unsupported target for user-semantic load.");
}
void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, llvm::Value *Source,
const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
if (CGM.getTarget().getTriple().isSPIRV())
return emitSPIRVUserSemanticStore(B, Source, Decl, Semantic, Index);
if (CGM.getTarget().getTriple().isDXIL())
return emitDXILUserSemanticStore(B, Source, Semantic, Index);
llvm_unreachable("Unsupported target for user-semantic load.");
}
llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
std::string SemanticName = Semantic->getAttrName()->getName().upper();
if (SemanticName == "SV_GROUPINDEX") {
llvm::Function *GroupIndex =
CGM.getIntrinsic(getFlattenedThreadIdInGroupIntrinsic());
return B.CreateCall(FunctionCallee(GroupIndex));
}
if (SemanticName == "SV_DISPATCHTHREADID") {
llvm::Intrinsic::ID IntrinID = getThreadIdIntrinsic();
llvm::Function *ThreadIDIntrinsic =
llvm::Intrinsic::isOverloaded(IntrinID)
? CGM.getIntrinsic(IntrinID, {CGM.Int32Ty})
: CGM.getIntrinsic(IntrinID);
return buildVectorInput(B, ThreadIDIntrinsic, Type);
}
if (SemanticName == "SV_GROUPTHREADID") {
llvm::Intrinsic::ID IntrinID = getGroupThreadIdIntrinsic();
llvm::Function *GroupThreadIDIntrinsic =
llvm::Intrinsic::isOverloaded(IntrinID)
? CGM.getIntrinsic(IntrinID, {CGM.Int32Ty})
: CGM.getIntrinsic(IntrinID);
return buildVectorInput(B, GroupThreadIDIntrinsic, Type);
}
if (SemanticName == "SV_GROUPID") {
llvm::Intrinsic::ID IntrinID = getGroupIdIntrinsic();
llvm::Function *GroupIDIntrinsic =
llvm::Intrinsic::isOverloaded(IntrinID)
? CGM.getIntrinsic(IntrinID, {CGM.Int32Ty})
: CGM.getIntrinsic(IntrinID);
return buildVectorInput(B, GroupIDIntrinsic, Type);
}
const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
assert(ShaderAttr && "Entry point has no shader attribute");
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
if (SemanticName == "SV_POSITION") {
if (ST == Triple::EnvironmentType::Pixel) {
if (CGM.getTarget().getTriple().isSPIRV())
return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
Semantic->getAttrName()->getName(),
/* BuiltIn::FragCoord */ 15);
if (CGM.getTarget().getTriple().isDXIL())
return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
}
if (ST == Triple::EnvironmentType::Vertex) {
return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index);
}
}
if (SemanticName == "SV_VERTEXID") {
if (ST == Triple::EnvironmentType::Vertex) {
if (CGM.getTarget().getTriple().isSPIRV())
return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
Semantic->getAttrName()->getName(),
/* BuiltIn::VertexIndex */ 42);
else
return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
}
}
llvm_unreachable(
"Load hasn't been implemented yet for this system semantic. FIXME");
}
static void createSPIRVBuiltinStore(IRBuilder<> &B, llvm::Module &M,
llvm::Value *Source, const Twine &Name,
unsigned BuiltInID) {
auto *GV = new llvm::GlobalVariable(
M, Source->getType(), /* isConstant= */ false,
llvm::GlobalValue::ExternalLinkage,
/* Initializer= */ nullptr, Name, /* insertBefore= */ nullptr,
llvm::GlobalVariable::GeneralDynamicTLSModel,
/* AddressSpace */ 8, /* isExternallyInitialized= */ false);
addSPIRVBuiltinDecoration(GV, BuiltInID);
GV->setVisibility(llvm::GlobalValue::HiddenVisibility);
B.CreateStore(Source, GV);
}
void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
const clang::DeclaratorDecl *Decl,
HLSLAppliedSemanticAttr *Semantic,
std::optional<unsigned> Index) {
std::string SemanticName = Semantic->getAttrName()->getName().upper();
if (SemanticName == "SV_POSITION") {
if (CGM.getTarget().getTriple().isDXIL()) {
emitDXILUserSemanticStore(B, Source, Semantic, Index);
return;
}
if (CGM.getTarget().getTriple().isSPIRV()) {
createSPIRVBuiltinStore(B, CGM.getModule(), Source,
Semantic->getAttrName()->getName(),
/* BuiltIn::Position */ 0);
return;
}
}
if (SemanticName == "SV_TARGET") {
emitUserSemanticStore(B, Source, Decl, Semantic, Index);
return;
}
llvm_unreachable(
"Store hasn't been implemented yet for this system semantic. FIXME");
}
llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
std::optional<unsigned> Index = Semantic->getSemanticIndex();
if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
return emitSystemSemanticLoad(B, FD, Type, Decl, Semantic, Index);
return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index);
}
void CGHLSLRuntime::handleScalarSemanticStore(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic) {
std::optional<unsigned> Index = Semantic->getSemanticIndex();
if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
emitSystemSemanticStore(B, Source, Decl, Semantic, Index);
else
emitUserSemanticStore(B, Source, Decl, Semantic, Index);
}
std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
CGHLSLRuntime::handleStructSemanticLoad(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
const llvm::StructType *ST = cast<StructType>(Type);
const clang::RecordDecl *RD = Decl->getType()->getAsRecordDecl();
assert(RD->getNumFields() == ST->getNumElements());
llvm::Value *Aggregate = llvm::PoisonValue::get(Type);
auto FieldDecl = RD->field_begin();
for (unsigned I = 0; I < ST->getNumElements(); ++I) {
auto [ChildValue, NextAttr] = handleSemanticLoad(
B, FD, ST->getElementType(I), *FieldDecl, AttrBegin, AttrEnd);
AttrBegin = NextAttr;
assert(ChildValue);
Aggregate = B.CreateInsertValue(Aggregate, ChildValue, I);
++FieldDecl;
}
return std::make_pair(Aggregate, AttrBegin);
}
specific_attr_iterator<HLSLAppliedSemanticAttr>
CGHLSLRuntime::handleStructSemanticStore(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
const clang::DeclaratorDecl *Decl,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
const llvm::StructType *ST = cast<StructType>(Source->getType());
const clang::RecordDecl *RD = nullptr;
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Decl))
RD = FD->getDeclaredReturnType()->getAsRecordDecl();
else
RD = Decl->getType()->getAsRecordDecl();
assert(RD);
assert(RD->getNumFields() == ST->getNumElements());
auto FieldDecl = RD->field_begin();
for (unsigned I = 0; I < ST->getNumElements(); ++I, ++FieldDecl) {
llvm::Value *Extract = B.CreateExtractValue(Source, I);
AttrBegin =
handleSemanticStore(B, FD, Extract, *FieldDecl, AttrBegin, AttrEnd);
}
return AttrBegin;
}
std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
CGHLSLRuntime::handleSemanticLoad(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
const clang::DeclaratorDecl *Decl,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
assert(AttrBegin != AttrEnd);
if (Type->isStructTy())
return handleStructSemanticLoad(B, FD, Type, Decl, AttrBegin, AttrEnd);
HLSLAppliedSemanticAttr *Attr = *AttrBegin;
++AttrBegin;
return std::make_pair(handleScalarSemanticLoad(B, FD, Type, Decl, Attr),
AttrBegin);
}
specific_attr_iterator<HLSLAppliedSemanticAttr>
CGHLSLRuntime::handleSemanticStore(
IRBuilder<> &B, const FunctionDecl *FD, llvm::Value *Source,
const clang::DeclaratorDecl *Decl,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrBegin,
specific_attr_iterator<HLSLAppliedSemanticAttr> AttrEnd) {
assert(AttrBegin != AttrEnd);
if (Source->getType()->isStructTy())
return handleStructSemanticStore(B, FD, Source, Decl, AttrBegin, AttrEnd);
HLSLAppliedSemanticAttr *Attr = *AttrBegin;
++AttrBegin;
handleScalarSemanticStore(B, FD, Source, Decl, Attr);
return AttrBegin;
}
void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
llvm::Function *Fn) {
llvm::Module &M = CGM.getModule();
llvm::LLVMContext &Ctx = M.getContext();
auto *EntryTy = llvm::FunctionType::get(llvm::Type::getVoidTy(Ctx), false);
Function *EntryFn =
Function::Create(EntryTy, Function::ExternalLinkage, FD->getName(), &M);
// Copy function attributes over, we have no argument or return attributes
// that can be valid on the real entry.
AttributeList NewAttrs = AttributeList::get(Ctx, AttributeList::FunctionIndex,
Fn->getAttributes().getFnAttrs());
EntryFn->setAttributes(NewAttrs);
setHLSLEntryAttributes(FD, EntryFn);
// Set the called function as internal linkage.
Fn->setLinkage(GlobalValue::InternalLinkage);
BasicBlock *BB = BasicBlock::Create(Ctx, "entry", EntryFn);
IRBuilder<> B(BB);
llvm::SmallVector<Value *> Args;
SmallVector<OperandBundleDef, 1> OB;
if (CGM.shouldEmitConvergenceTokens()) {
assert(EntryFn->isConvergent());
llvm::Value *I =
B.CreateIntrinsic(llvm::Intrinsic::experimental_convergence_entry, {});
llvm::Value *bundleArgs[] = {I};
OB.emplace_back("convergencectrl", bundleArgs);
}
SmallVector<std::pair<llvm::Value *, llvm::Type *>> OutputSemantic;
unsigned SRetOffset = 0;
for (const auto &Param : Fn->args()) {
if (Param.hasStructRetAttr()) {
SRetOffset = 1;
llvm::Type *VarType = Param.getParamStructRetType();
llvm::Value *Var =
CGM.getLangOpts().EmitLogicalPointer
? cast<Instruction>(B.CreateStructuredAlloca(VarType))
: cast<Instruction>(B.CreateAlloca(VarType));
OutputSemantic.push_back(std::make_pair(Var, VarType));
Args.push_back(Var);
continue;
}
const ParmVarDecl *PD = FD->getParamDecl(Param.getArgNo() - SRetOffset);
llvm::Value *SemanticValue = nullptr;
// FIXME: support inout/out parameters for semantics.
if ([[maybe_unused]] HLSLParamModifierAttr *MA =
PD->getAttr<HLSLParamModifierAttr>()) {
llvm_unreachable("Not handled yet");
} else {
llvm::Type *ParamType = nullptr;
if (Param.hasByValAttr())
ParamType = Param.getParamByValType();
else if (PD->getType()->isRecordType())
ParamType = CGM.getTypes().ConvertType(PD->getType());
else
ParamType = Param.getType();
auto AttrBegin = PD->specific_attr_begin<HLSLAppliedSemanticAttr>();
auto AttrEnd = PD->specific_attr_end<HLSLAppliedSemanticAttr>();
auto Result =
handleSemanticLoad(B, FD, ParamType, PD, AttrBegin, AttrEnd);
SemanticValue = Result.first;
if (!SemanticValue)
return;
if (Param.hasByValAttr() || PD->getType()->isRecordType()) {
llvm::Value *Var =
CGM.getLangOpts().EmitLogicalPointer
? cast<Instruction>(B.CreateStructuredAlloca(ParamType))
: cast<Instruction>(B.CreateAlloca(ParamType));
B.CreateStore(SemanticValue, Var);
SemanticValue = Var;
}
}
assert(SemanticValue);
Args.push_back(SemanticValue);
}
CallInst *CI = B.CreateCall(FunctionCallee(Fn), Args, OB);
CI->setCallingConv(Fn->getCallingConv());
if (Fn->getReturnType() != CGM.VoidTy)
// Element type is unused, so set to dummy value (NULL).
OutputSemantic.push_back(std::make_pair(CI, nullptr));
for (auto &SourcePair : OutputSemantic) {
llvm::Value *Source = SourcePair.first;
llvm::Type *ElementType = SourcePair.second;
AllocaInst *AI = dyn_cast<AllocaInst>(Source);
llvm::Value *SourceValue = AI ? B.CreateLoad(ElementType, Source) : Source;
auto AttrBegin = FD->specific_attr_begin<HLSLAppliedSemanticAttr>();
auto AttrEnd = FD->specific_attr_end<HLSLAppliedSemanticAttr>();
handleSemanticStore(B, FD, SourceValue, FD, AttrBegin, AttrEnd);
}
B.CreateRetVoid();
// Add and identify root signature to function, if applicable
for (const Attr *Attr : FD->getAttrs()) {
if (const auto *RSAttr = dyn_cast<RootSignatureAttr>(Attr)) {
auto *RSDecl = RSAttr->getSignatureDecl();
addRootSignatureMD(RSDecl->getVersion(), RSDecl->getRootElements(),
EntryFn, M);
}
}
}
static void gatherFunctions(SmallVectorImpl<Function *> &Fns, llvm::Module &M,
bool CtorOrDtor) {
const auto *GV =
M.getNamedGlobal(CtorOrDtor ? "llvm.global_ctors" : "llvm.global_dtors");
if (!GV)
return;
const auto *CA = dyn_cast<ConstantArray>(GV->getInitializer());
if (!CA)
return;
// The global_ctor array elements are a struct [Priority, Fn *, COMDat].
// HLSL neither supports priorities or COMDat values, so we will check those
// in an assert but not handle them.
for (const auto &Ctor : CA->operands()) {
if (isa<ConstantAggregateZero>(Ctor))
continue;
ConstantStruct *CS = cast<ConstantStruct>(Ctor);
assert(cast<ConstantInt>(CS->getOperand(0))->getValue() == 65535 &&
"HLSL doesn't support setting priority for global ctors.");
assert(isa<ConstantPointerNull>(CS->getOperand(2)) &&
"HLSL doesn't support COMDat for global ctors.");
Fns.push_back(cast<Function>(CS->getOperand(1)));
}
}
void CGHLSLRuntime::generateGlobalCtorDtorCalls() {
llvm::Module &M = CGM.getModule();
SmallVector<Function *> CtorFns;
SmallVector<Function *> DtorFns;
gatherFunctions(CtorFns, M, true);
gatherFunctions(DtorFns, M, false);
// Insert a call to the global constructor at the beginning of the entry block
// to externally exported functions. This is a bit of a hack, but HLSL allows
// global constructors, but doesn't support driver initialization of globals.
for (auto &F : M.functions()) {
if (!F.hasFnAttribute("hlsl.shader"))
continue;
auto *Token = getConvergenceToken(F.getEntryBlock());
Instruction *IP = &*F.getEntryBlock().begin();
SmallVector<OperandBundleDef, 1> OB;
if (Token) {
llvm::Value *bundleArgs[] = {Token};
OB.emplace_back("convergencectrl", bundleArgs);
IP = Token->getNextNode();
}
IRBuilder<> B(IP);
for (auto *Fn : CtorFns) {
auto CI = B.CreateCall(FunctionCallee(Fn), {}, OB);
CI->setCallingConv(Fn->getCallingConv());
}
// Insert global dtors before the terminator of the last instruction
B.SetInsertPoint(F.back().getTerminator());
for (auto *Fn : DtorFns) {
auto CI = B.CreateCall(FunctionCallee(Fn), {}, OB);
CI->setCallingConv(Fn->getCallingConv());
}
}
// No need to keep global ctors/dtors for non-lib profile after call to
// ctors/dtors added for entry.
Triple T(M.getTargetTriple());
if (T.getEnvironment() != Triple::EnvironmentType::Library) {
if (auto *GV = M.getNamedGlobal("llvm.global_ctors"))
GV->eraseFromParent();
if (auto *GV = M.getNamedGlobal("llvm.global_dtors"))
GV->eraseFromParent();
}
}
static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV,
Intrinsic::ID IntrID,
ArrayRef<llvm::Value *> Args) {
LLVMContext &Ctx = CGM.getLLVMContext();
llvm::Function *InitResFunc =
llvm::Function::Create(llvm::FunctionType::get(CGM.VoidTy, false),
llvm::GlobalValue::InternalLinkage,
"_init_buffer_" + GV->getName(), CGM.getModule());
InitResFunc->addFnAttr(llvm::Attribute::AlwaysInline);
llvm::BasicBlock *EntryBB =
llvm::BasicBlock::Create(Ctx, "entry", InitResFunc);
CGBuilderTy Builder(CGM, Ctx);
const DataLayout &DL = CGM.getModule().getDataLayout();
Builder.SetInsertPoint(EntryBB);
// Make sure the global variable is buffer resource handle
llvm::Type *HandleTy = GV->getValueType();
assert(HandleTy->isTargetExtTy() && "unexpected type of the buffer global");
llvm::Value *CreateHandle = Builder.CreateIntrinsic(
/*ReturnType=*/HandleTy, IntrID, Args, nullptr,
Twine(GV->getName()).concat("_h"));
Builder.CreateAlignedStore(CreateHandle, GV, GV->getPointerAlignment(DL));
Builder.CreateRetVoid();
CGM.AddCXXGlobalInit(InitResFunc);
}
void CGHLSLRuntime::initializeBufferFromBinding(const HLSLBufferDecl *BufDecl,
llvm::GlobalVariable *GV) {
ResourceBindingAttrs Binding(BufDecl);
assert(Binding.hasBinding() &&
"cbuffer/tbuffer should always have resource binding attribute");
auto *Index = llvm::ConstantInt::get(CGM.IntTy, 0);
auto *RangeSize = llvm::ConstantInt::get(CGM.IntTy, 1);
auto *Space = llvm::ConstantInt::get(CGM.IntTy, Binding.getSpace());
Value *Name = buildNameForResource(BufDecl->getName(), CGM);
// buffer with explicit binding
if (Binding.isExplicit()) {
llvm::Intrinsic::ID IntrinsicID =
CGM.getHLSLRuntime().getCreateHandleFromBindingIntrinsic();
auto *RegSlot = llvm::ConstantInt::get(CGM.IntTy, Binding.getSlot());
SmallVector<Value *> Args{Space, RegSlot, RangeSize, Index, Name};
initializeBuffer(CGM, GV, IntrinsicID, Args);
} else {
// buffer with implicit binding
llvm::Intrinsic::ID IntrinsicID =
CGM.getHLSLRuntime().getCreateHandleFromImplicitBindingIntrinsic();
auto *OrderID =
llvm::ConstantInt::get(CGM.IntTy, Binding.getImplicitOrderID());
SmallVector<Value *> Args{OrderID, Space, RangeSize, Index, Name};
initializeBuffer(CGM, GV, IntrinsicID, Args);
}
}
void CGHLSLRuntime::handleGlobalVarDefinition(const VarDecl *VD,
llvm::GlobalVariable *GV) {
if (auto Attr = VD->getAttr<HLSLVkExtBuiltinInputAttr>())
addSPIRVBuiltinDecoration(GV, Attr->getBuiltIn());
if (auto Attr = VD->getAttr<HLSLVkExtBuiltinOutputAttr>())
addSPIRVBuiltinDecoration(GV, Attr->getBuiltIn());
}
llvm::Instruction *CGHLSLRuntime::getConvergenceToken(BasicBlock &BB) {
if (!CGM.shouldEmitConvergenceTokens())
return nullptr;
auto E = BB.end();
for (auto I = BB.begin(); I != E; ++I) {
auto *II = dyn_cast<llvm::IntrinsicInst>(&*I);
if (II && llvm::isConvergenceControlIntrinsic(II->getIntrinsicID())) {
return II;
}
}
llvm_unreachable("Convergence token should have been emitted.");
return nullptr;
}
class OpaqueValueVisitor : public RecursiveASTVisitor<OpaqueValueVisitor> {
public:
llvm::SmallVector<OpaqueValueExpr *, 8> OVEs;
llvm::SmallPtrSet<OpaqueValueExpr *, 8> Visited;
OpaqueValueVisitor() {}
bool VisitHLSLOutArgExpr(HLSLOutArgExpr *) {
// These need to be bound in CodeGenFunction::EmitHLSLOutArgLValues
// or CodeGenFunction::EmitHLSLOutArgExpr. If they are part of this
// traversal, the temporary containing the copy out will not have
// been created yet.
return false;
}
bool VisitOpaqueValueExpr(OpaqueValueExpr *E) {
// Traverse the source expression first.
if (E->getSourceExpr())
TraverseStmt(E->getSourceExpr());
// Then add this OVE if we haven't seen it before.
if (Visited.insert(E).second)
OVEs.push_back(E);
return true;
}
};
void CGHLSLRuntime::emitInitListOpaqueValues(CodeGenFunction &CGF,
InitListExpr *E) {
typedef CodeGenFunction::OpaqueValueMappingData OpaqueValueMappingData;
OpaqueValueVisitor Visitor;
Visitor.TraverseStmt(E);
for (auto *OVE : Visitor.OVEs) {
if (CGF.isOpaqueValueEmitted(OVE))
continue;
if (OpaqueValueMappingData::shouldBindAsLValue(OVE)) {
LValue LV = CGF.EmitLValue(OVE->getSourceExpr());
OpaqueValueMappingData::bind(CGF, OVE, LV);
} else {
RValue RV = CGF.EmitAnyExpr(OVE->getSourceExpr());
OpaqueValueMappingData::bind(CGF, OVE, RV);
}
}
}
std::optional<LValue> CGHLSLRuntime::emitResourceArraySubscriptExpr(
const ArraySubscriptExpr *ArraySubsExpr, CodeGenFunction &CGF) {
assert((ArraySubsExpr->getType()->isHLSLResourceRecord() ||
ArraySubsExpr->getType()->isHLSLResourceRecordArray()) &&
"expected resource array subscript expression");
// Let clang codegen handle local and static resource array subscripts,
// or when the subscript references on opaque expression (as part of
// ArrayInitLoopExpr AST node).
const VarDecl *ArrayDecl = dyn_cast_or_null<VarDecl>(
getArrayDecl(CGF.CGM.getContext(), ArraySubsExpr));
if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
ArrayDecl->getStorageClass() == SC_Static)
return std::nullopt;
// get the resource array type
ASTContext &AST = ArrayDecl->getASTContext();
const Type *ResArrayTy = ArrayDecl->getType().getTypePtr();
assert(ResArrayTy->isHLSLResourceRecordArray() &&
"expected array of resource classes");
// Iterate through all nested array subscript expressions to calculate
// the index in the flattened resource array (if this is a multi-
// dimensional array). The index is calculated as a sum of all indices
// multiplied by the total size of the array at that level.
Value *Index = nullptr;
const ArraySubscriptExpr *ASE = ArraySubsExpr;
while (ASE != nullptr) {
Value *SubIndex = CGF.EmitScalarExpr(ASE->getIdx());
if (const auto *ArrayTy =
dyn_cast<ConstantArrayType>(ASE->getType().getTypePtr())) {
Value *Multiplier = llvm::ConstantInt::get(
CGM.IntTy, AST.getConstantArrayElementCount(ArrayTy));
SubIndex = CGF.Builder.CreateMul(SubIndex, Multiplier);
}
Index = Index ? CGF.Builder.CreateAdd(Index, SubIndex) : SubIndex;
ASE = dyn_cast<ArraySubscriptExpr>(ASE->getBase()->IgnoreParenImpCasts());
}
// Find binding info for the resource array. For implicit binding
// an HLSLResourceBindingAttr should have been added by SemaHLSL.
ResourceBindingAttrs Binding(ArrayDecl);
assert(Binding.hasBinding() &&
"resource array must have a binding attribute");
// Find the individual resource type.
QualType ResultTy = ArraySubsExpr->getType();
QualType ResourceTy =
ResultTy->isArrayType() ? AST.getBaseElementType(ResultTy) : ResultTy;
// Create a temporary variable for the result, which is either going
// to be a single resource instance or a local array of resources (we need to
// return an LValue).
RawAddress TmpVar = CGF.CreateMemTempWithoutCast(ResultTy);
if (CGF.EmitLifetimeStart(TmpVar.getPointer()))
CGF.pushFullExprCleanup<CodeGenFunction::CallLifetimeEnd>(
NormalEHLifetimeMarker, TmpVar);
AggValueSlot ValueSlot = AggValueSlot::forAddr(
TmpVar, Qualifiers(), AggValueSlot::IsDestructed_t(true),
AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsAliased_t(false),
AggValueSlot::DoesNotOverlap);
// Calculate total array size (= range size).
llvm::Value *Range = llvm::ConstantInt::getSigned(
CGM.IntTy, getTotalArraySize(AST, ResArrayTy));
// If the result of the subscript operation is a single resource, call the
// constructor.
if (ResultTy == ResourceTy) {
CallArgList Args;
CXXMethodDecl *CreateMethod = lookupResourceInitMethodAndSetupArgs(
CGF.CGM, ResourceTy->getAsCXXRecordDecl(), Range, Index,
ArrayDecl->getName(), Binding, Args);
if (!CreateMethod) {
// This can happen if someone creates an array of structs that looks like
// an HLSL resource record array but it does not have the required static
// create method. No binding will be generated for it.
assert(!ResourceTy->getAsCXXRecordDecl()->isImplicit() &&
"create method lookup should always succeed for built-in resource "
"records");
return std::nullopt;
}
callResourceInitMethod(CGF, CreateMethod, Args, ValueSlot.getAddress());
} else {
// The result of the subscript operation is a local resource array which
// needs to be initialized.
const ConstantArrayType *ArrayTy =
cast<ConstantArrayType>(ResultTy.getTypePtr());
std::optional<llvm::Value *> EndIndex = initializeResourceArrayFromGlobal(
CGF, ResourceTy->getAsCXXRecordDecl(), ArrayTy, ValueSlot, Range, Index,
ArrayDecl->getName(), Binding, {llvm::ConstantInt::get(CGM.IntTy, 0)});
if (!EndIndex)
return std::nullopt;
}
return CGF.MakeAddrLValue(TmpVar, ResultTy, AlignmentSource::Decl);
}
// Initialize all resources of a global resource array into provided slot.
bool CGHLSLRuntime::initializeGlobalResourceArray(CodeGenFunction &CGF,
const VarDecl *ArrayDecl,
AggValueSlot &DestSlot) {
assert(ArrayDecl->getType()->isHLSLResourceRecordArray() &&
ArrayDecl->hasGlobalStorage() &&
ArrayDecl->getStorageClass() != SC_Static &&
"expected global non-static resource array");
// Find binding info for the resource array. For implicit binding
// the HLSLResourceBindingAttr should have been added by SemaHLSL.
ResourceBindingAttrs Binding(ArrayDecl);
assert(Binding.hasBinding() &&
"resource array must have a binding attribute");
// Find the individual resource type.
ASTContext &AST = ArrayDecl->getASTContext();
QualType ResTy = AST.getBaseElementType(ArrayDecl->getType());
const auto *ResArrayTy =
cast<ConstantArrayType>(ArrayDecl->getType().getTypePtr());
// Create Value for index and total array size (= range size).
int Size = getTotalArraySize(AST, ResArrayTy);
llvm::Value *Zero = llvm::ConstantInt::get(CGM.IntTy, 0);
llvm::Value *Range = llvm::ConstantInt::get(CGM.IntTy, Size);
// Initialize individual resources in the array into DestSlot.
std::optional<llvm::Value *> EndIndex = initializeResourceArrayFromGlobal(
CGF, ResTy->getAsCXXRecordDecl(), ResArrayTy, DestSlot, Range, Zero,
ArrayDecl->getName(), Binding, {Zero});
return EndIndex.has_value();
}
// If the expression is a global resource array, initialize all of its resources
// into Dest. Returns false if no initialization has been performed and the
// array copy should be handled by the default codegen.
bool CGHLSLRuntime::emitGlobalResourceArray(CodeGenFunction &CGF, const Expr *E,
AggValueSlot &DestSlot) {
assert(E->getType()->isHLSLResourceRecordArray() &&
"expected resource array");
// Find the array declaration for the expression. Fallback to the default
// handling if it's not a global resource array.
const VarDecl *ArrayDecl =
dyn_cast_or_null<VarDecl>(getArrayDecl(CGF.CGM.getContext(), E));
if (!ArrayDecl || !ArrayDecl->hasGlobalStorage() ||
ArrayDecl->getStorageClass() == SC_Static)
return false;
return initializeGlobalResourceArray(CGF, ArrayDecl, DestSlot);
}
// If the expression is a global resource array, create a temporary and
// initialize all of its resources, and return it as an LValue. Returns nullopt
// if no initialization has been performed and the handling should follow the
// default path.
std::optional<LValue>
CGHLSLRuntime::emitGlobalResourceArrayAsLValue(CodeGenFunction &CGF,
const VarDecl *ArrayDecl) {
assert(ArrayDecl->getType()->isHLSLResourceRecordArray() &&
"expected resource array declaration");
if (!ArrayDecl->hasGlobalStorage() ||
ArrayDecl->getStorageClass() == SC_Static)
return std::nullopt;
AggValueSlot TmpArraySlot =
CGF.CreateAggTemp(ArrayDecl->getType(), "tmpResArray");
if (initializeGlobalResourceArray(CGF, ArrayDecl, TmpArraySlot))
return CGF.MakeAddrLValue(TmpArraySlot.getAddress(), ArrayDecl->getType(),
AlignmentSource::Decl);
return std::nullopt;
}
RawAddress CGHLSLRuntime::createBufferMatrixTempAddress(const LValue &LV,
CodeGenFunction &CGF) {
assert(LV.getType()->isConstantMatrixType() && "expected matrix type");
assert(LV.getType().getAddressSpace() == LangAS::hlsl_constant &&
"expected cbuffer matrix");
QualType MatQualTy = LV.getType();
llvm::Type *LayoutTy = HLSLBufferLayoutBuilder(CGF.CGM).layOutType(MatQualTy);
Address SrcAddr = LV.getAddress();
if (LayoutTy == CGF.ConvertTypeForMem(MatQualTy))
return SrcAddr;
RawAddress DestAlloca =
CGF.CreateMemTempWithoutCast(MatQualTy, "matrix.buf.copy");
HLSLBufferCopyEmitter(CGF, DestAlloca, SrcAddr).emitCopy(MatQualTy);
return DestAlloca;
}
std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr(
const ArraySubscriptExpr *E, CodeGenFunction &CGF,
llvm::function_ref<llvm::Value *(bool Promote)> EmitIdxAfterBase) {
// Find the element type to index by first padding the element type per HLSL
// buffer rules, and then padding out to a 16-byte register boundary if
// necessary.
llvm::Type *LayoutTy =
HLSLBufferLayoutBuilder(CGF.CGM).layOutType(E->getType());
uint64_t LayoutSizeInBits =
CGM.getDataLayout().getTypeSizeInBits(LayoutTy).getFixedValue();
CharUnits ElementSize = CharUnits::fromQuantity(LayoutSizeInBits / 8);
CharUnits RowAlignedSize = ElementSize.alignTo(CharUnits::fromQuantity(16));
if (RowAlignedSize > ElementSize) {
llvm::Type *Padding = CGM.getTargetCodeGenInfo().getHLSLPadding(
CGM, RowAlignedSize - ElementSize);
assert(Padding && "No padding type for target?");
LayoutTy = llvm::StructType::get(CGF.getLLVMContext(), {LayoutTy, Padding},
/*isPacked=*/true);
}
// If the layout type doesn't introduce any padding, we don't need to do
// anything special.
llvm::Type *OrigTy = CGF.CGM.getTypes().ConvertTypeForMem(E->getType());
if (LayoutTy == OrigTy)
return std::nullopt;
LValueBaseInfo EltBaseInfo;
TBAAAccessInfo EltTBAAInfo;
// Index into the object as-if we have an array of the padded element type,
// and then dereference the element itself to avoid reading padding that may
// be past the end of the in-memory object.
SmallVector<llvm::Value *, 2> Indices;
llvm::Value *Idx = EmitIdxAfterBase(/*Promote*/ true);
Indices.push_back(Idx);
Indices.push_back(llvm::ConstantInt::get(CGF.Int32Ty, 0));
if (CGF.getLangOpts().EmitLogicalPointer) {
// The fact that we emit an array-to-pointer decay might be an oversight,
// but for now, we simply ignore it (see #179951).
const CastExpr *CE = cast<CastExpr>(E->getBase());
assert(CE->getCastKind() == CastKind::CK_ArrayToPointerDecay);
LValue LV = CGF.EmitLValue(CE->getSubExpr());
Address Addr = LV.getAddress();
LayoutTy = llvm::ArrayType::get(
LayoutTy,
cast<llvm::ArrayType>(Addr.getElementType())->getNumElements());
auto *GEP = cast<StructuredGEPInst>(CGF.Builder.CreateStructuredGEP(
LayoutTy, Addr.emitRawPointer(CGF), Indices, "cbufferidx"));
Addr =
Address(GEP, GEP->getResultElementType(), RowAlignedSize, KnownNonNull);
return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
}
Address Addr =
CGF.EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
llvm::Value *GEP = CGF.Builder.CreateGEP(LayoutTy, Addr.emitRawPointer(CGF),
Indices, "cbufferidx");
Addr = Address(GEP, Addr.getElementType(), RowAlignedSize, KnownNonNull);
return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
}
std::optional<LValue>
CGHLSLRuntime::emitResourceMemberExpr(CodeGenFunction &CGF,
const MemberExpr *ME) {
assert((ME->getType()->isHLSLResourceRecord() ||
ME->getType()->isHLSLResourceRecordArray()) &&
"expected resource member expression");
const VarDecl *ResourceVD =
findAssociatedResourceDeclForStruct(CGF.CGM.getContext(), ME);
if (!ResourceVD)
return std::nullopt;
// Handle member of resource array type.
if (ResourceVD->getType()->isHLSLResourceRecordArray())
return emitGlobalResourceArrayAsLValue(CGF, ResourceVD);
GlobalVariable *ResGV =
cast<GlobalVariable>(CGM.GetAddrOfGlobalVar(ResourceVD));
const DataLayout &DL = CGM.getDataLayout();
llvm::Type *Ty = ResGV->getValueType();
CharUnits Align = CharUnits::fromQuantity(DL.getABITypeAlign(Ty));
Address Addr = Address(ResGV, Ty, Align);
LValue LV = LValue::MakeAddr(Addr, ME->getType(), CGM.getContext(),
LValueBaseInfo(AlignmentSource::Type),
CGM.getTBAAAccessInfo(ME->getType()));
return LV;
}
bool CGHLSLRuntime::emitBufferCopy(CodeGenFunction &CGF, const Expr *E,
const LValue &SrcLV,
AggValueSlot &DestSlot) {
assert(E->getType().getAddressSpace() == LangAS::hlsl_constant &&
"expected expression in HLSL constant address space");
assert(!E->getType()->isHLSLResourceRecord() &&
!E->getType()->isHLSLResourceRecordArray() &&
"direct accesses to resource types should be handled separately");
if (DestSlot.isIgnored())
return false;
QualType Ty = E->getType();
Address DstPtr = DestSlot.getAddress();
Address SrcPtr = SrcLV.getAddress();
// If there are no intangible types, we don't need to lookup associated
// resources.
if (!Ty->isHLSLIntangibleType())
return HLSLBufferCopyEmitter(CGF, DstPtr, SrcPtr).emitCopy(Ty);
// Handle structs with intangible types by setting the resource fields
// of the destination struct with the resources associated with the global
// struct.
EmbeddedResourceNameBuilder NameBuilder;
const VarDecl *VD = findStructResourceParentDeclAndBuildName(E, NameBuilder);
AssociatedResourcesList AssociatedResources(VD, NameBuilder.getName());
// Callback to fill in the associated resource.
auto EmitResFn = [&](AggValueSlot &ResSlot) {
const VarDecl *ResDecl = AssociatedResources.getNextResource();
assert(ResDecl && "associated resource declaration not found");
// Check that the resource type of dest and src matches.
[[maybe_unused]] llvm::Type *DestType =
ResSlot.getAddress().getElementType();
[[maybe_unused]] llvm::Type *SrcConvertedType =
CGM.getTypes().ConvertTypeForMem(ResDecl->getType());
assert(DestType == SrcConvertedType && "resource slot type mismatch");
if (ResDecl->getType()->isHLSLResourceRecord())
copyGlobalResource(CGF, ResDecl, ResSlot);
else
initializeGlobalResourceArray(CGF, ResDecl, ResSlot);
};
auto Result =
HLSLBufferCopyEmitter(CGF, DstPtr, SrcPtr).emitCopy(Ty, EmitResFn);
assert(AssociatedResources.getNextResource() == nullptr &&
"expected all associated resources to be processed");
return Result;
}
LValue CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF,
const MemberExpr *E) {
LValue Base =
CGF.EmitCheckedLValue(E->getBase(), CodeGenFunction::TCK_MemberAccess);
auto *Field = dyn_cast<FieldDecl>(E->getMemberDecl());
assert(Field && "Unexpected access into HLSL buffer");
const RecordDecl *Rec = Field->getParent();
// Work out the buffer layout type to index into.
QualType RecType = CGM.getContext().getCanonicalTagType(Rec);
assert(RecType->isStructureOrClassType() && "Invalid type in HLSL buffer");
// Since this is a member of an object in the buffer and not the buffer's
// struct/class itself, we shouldn't have any offsets on the members we need
// to contend with.
CGHLSLOffsetInfo EmptyOffsets;
llvm::StructType *LayoutTy = HLSLBufferLayoutBuilder(CGM).layOutStruct(
RecType->getAsCanonical<RecordType>(), EmptyOffsets);
// Get the field index for the layout struct, accounting for padding.
unsigned FieldIdx =
CGM.getTypes().getCGRecordLayout(Rec).getLLVMFieldNo(Field);
assert(FieldIdx < LayoutTy->getNumElements() &&
"Layout struct is smaller than member struct");
unsigned Skipped = 0;
for (unsigned I = 0; I <= FieldIdx;) {
llvm::Type *ElementTy = LayoutTy->getElementType(I + Skipped);
if (CGF.CGM.getTargetCodeGenInfo().isHLSLPadding(ElementTy))
++Skipped;
else
++I;
}
FieldIdx += Skipped;
assert(FieldIdx < LayoutTy->getNumElements() && "Access out of bounds");
// Now index into the struct, making sure that the type we return is the
// buffer layout type rather than the original type in the AST.
QualType FieldType = Field->getType();
llvm::Type *FieldLLVMTy = CGM.getTypes().ConvertTypeForMem(FieldType);
CharUnits Align = CharUnits::fromQuantity(
CGF.CGM.getDataLayout().getABITypeAlign(FieldLLVMTy));
Value *Ptr = CGF.getLangOpts().EmitLogicalPointer
? CGF.Builder.CreateStructuredGEP(
LayoutTy, Base.getPointer(CGF),
llvm::ConstantInt::get(CGM.IntTy, FieldIdx))
: CGF.Builder.CreateStructGEP(LayoutTy, Base.getPointer(CGF),
FieldIdx, Field->getName());
Address Addr(Ptr, FieldLLVMTy, Align, KnownNonNull);
LValue LV = LValue::MakeAddr(Addr, FieldType, CGM.getContext(),
LValueBaseInfo(AlignmentSource::Type),
CGM.getTBAAAccessInfo(FieldType));
LV.getQuals().addCVRQualifiers(Base.getVRQualifiers());
return LV;
}