blob: 29fa33f847e999189b64d44ef994abbff49824bb [file] [log] [blame] [edit]
///////////////////////////////////////////////////////////////////////////////
// //
// DxilUtil.cpp //
// Copyright (C) Microsoft Corporation. All rights reserved. //
// This file is distributed under the University of Illinois Open Source //
// License. See LICENSE.TXT for details. //
// //
// Dxil helper functions. //
// //
///////////////////////////////////////////////////////////////////////////////
#include "dxc/DXIL/DxilUtil.h"
#include "dxc/DXIL/DxilInstructions.h"
#include "dxc/DXIL/DxilModule.h"
#include "dxc/DXIL/DxilOperations.h"
#include "dxc/DXIL/DxilTypeSystem.h"
#include "dxc/HLSL/DxilConvergentName.h"
#include "dxc/Support/Global.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/IR/GetElementPtrTypeIterator.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
using namespace hlsl;
namespace hlsl {
namespace dxilutil {
const char ManglingPrefix[] = "\01?";
const char EntryPrefix[] = "dx.entry.";
Type *GetArrayEltTy(Type *Ty) {
if (isa<PointerType>(Ty))
Ty = Ty->getPointerElementType();
while (isa<ArrayType>(Ty)) {
Ty = Ty->getArrayElementType();
}
return Ty;
}
bool HasDynamicIndexing(Value *V) {
for (auto User : V->users()) {
if (GetElementPtrInst *GEP = dyn_cast<GetElementPtrInst>(User)) {
for (auto Idx = GEP->idx_begin(); Idx != GEP->idx_end(); ++Idx) {
if (!isa<ConstantInt>(Idx))
return true;
}
}
}
return false;
}
unsigned GetLegacyCBufferFieldElementSize(DxilFieldAnnotation &fieldAnnotation,
llvm::Type *Ty,
DxilTypeSystem &typeSys) {
while (isa<ArrayType>(Ty)) {
Ty = Ty->getArrayElementType();
}
// Bytes.
CompType compType = fieldAnnotation.GetCompType();
unsigned compSize = compType.Is64Bit() ? 8
: compType.Is16Bit() && !typeSys.UseMinPrecision() ? 2
: 4;
unsigned fieldSize = compSize;
if (Ty->isVectorTy()) {
fieldSize *= cast<FixedVectorType>(Ty)->getNumElements();
} else if (StructType *ST = dyn_cast<StructType>(Ty)) {
DxilStructAnnotation *EltAnnotation = typeSys.GetStructAnnotation(ST);
if (EltAnnotation) {
fieldSize = EltAnnotation->GetCBufferSize();
} else {
// Calculate size when don't have annotation.
if (fieldAnnotation.HasMatrixAnnotation()) {
const DxilMatrixAnnotation &matAnnotation =
fieldAnnotation.GetMatrixAnnotation();
unsigned rows = matAnnotation.Rows;
unsigned cols = matAnnotation.Cols;
if (matAnnotation.Orientation == MatrixOrientation::ColumnMajor) {
rows = cols;
cols = matAnnotation.Rows;
} else if (matAnnotation.Orientation != MatrixOrientation::RowMajor) {
// Invalid matrix orientation.
fieldSize = 0;
}
fieldSize = (rows - 1) * 16 + cols * 4;
} else {
// Cannot find struct annotation.
fieldSize = 0;
}
}
}
return fieldSize;
}
bool IsStaticGlobal(GlobalVariable *GV) {
return GV->getLinkage() == GlobalValue::LinkageTypes::InternalLinkage &&
GV->getType()->getPointerAddressSpace() == DXIL::kDefaultAddrSpace;
}
bool IsSharedMemoryGlobal(llvm::GlobalVariable *GV) {
return GV->getType()->getPointerAddressSpace() == DXIL::kTGSMAddrSpace;
}
bool RemoveUnusedFunctions(Module &M, Function *EntryFunc,
Function *PatchConstantFunc, bool IsLib) {
std::vector<Function *> deadList;
for (auto &F : M.functions()) {
if (&F == EntryFunc || &F == PatchConstantFunc)
continue;
if (F.isDeclaration() || !IsLib || F.hasInternalLinkage()) {
if (F.user_empty())
deadList.emplace_back(&F);
}
}
bool bUpdated = deadList.size();
for (Function *F : deadList)
F->eraseFromParent();
return bUpdated;
}
void PrintDiagnosticHandler(const llvm::DiagnosticInfo &DI, void *Context) {
DiagnosticPrinter *printer = reinterpret_cast<DiagnosticPrinter *>(Context);
DI.print(*printer);
}
StringRef DemangleFunctionName(StringRef name) {
if (!name.startswith(ManglingPrefix)) {
// Name isn't mangled.
return name;
}
size_t nameEnd = name.find_first_of("@");
DXASSERT(nameEnd != StringRef::npos, "else Name isn't mangled but has \01?");
return name.substr(2, nameEnd - 2);
}
std::string ReplaceFunctionName(StringRef originalName, StringRef newName) {
if (originalName.startswith(ManglingPrefix)) {
return (Twine(ManglingPrefix) + newName +
originalName.substr(originalName.find_first_of('@')))
.str();
} else if (originalName.startswith(EntryPrefix)) {
return (Twine(EntryPrefix) + newName).str();
}
return newName.str();
}
// From AsmWriter.cpp
// PrintEscapedString - Print each character of the specified string, escaping
// it if it is not printable or if it is an escape char.
void PrintEscapedString(StringRef Name, raw_ostream &Out) {
for (unsigned i = 0, e = Name.size(); i != e; ++i) {
unsigned char C = Name[i];
if (isprint(C) && C != '\\' && C != '"')
Out << C;
else
Out << '\\' << hexdigit(C >> 4) << hexdigit(C & 0x0F);
}
}
void PrintUnescapedString(StringRef Name, raw_ostream &Out) {
for (unsigned i = 0, e = Name.size(); i != e; ++i) {
unsigned char C = Name[i];
if (C == '\\') {
C = Name[++i];
unsigned value = hexDigitValue(C);
if (value != -1U) {
C = (unsigned char)value;
unsigned value2 = hexDigitValue(Name[i + 1]);
assert(value2 != -1U && "otherwise, not a two digit hex escape");
if (value2 != -1U) {
C = (C << 4) + (unsigned char)value2;
++i;
}
} // else, the next character (in C) should be the escaped character
}
Out << C;
}
}
void CollectSelect(llvm::Instruction *Inst,
std::unordered_set<llvm::Instruction *> &selectSet) {
unsigned startOpIdx = 0;
// Skip Cond for Select.
if (isa<SelectInst>(Inst)) {
startOpIdx = 1;
} else if (!isa<PHINode>(Inst)) {
// Only check phi and select here.
return;
}
// Already add.
if (selectSet.count(Inst))
return;
selectSet.insert(Inst);
// Scan operand to add node which is phi/select.
unsigned numOperands = Inst->getNumOperands();
for (unsigned i = startOpIdx; i < numOperands; i++) {
Value *V = Inst->getOperand(i);
if (Instruction *I = dyn_cast<Instruction>(V)) {
CollectSelect(I, selectSet);
}
}
}
Value *MergeSelectOnSameValue(Instruction *SelInst, unsigned startOpIdx,
unsigned numOperands) {
Value *op0 = nullptr;
for (unsigned i = startOpIdx; i < numOperands; i++) {
Value *op = SelInst->getOperand(i);
if (i == startOpIdx) {
op0 = op;
} else {
if (op0 != op)
return nullptr;
}
}
if (op0) {
SelInst->replaceAllUsesWith(op0);
SelInst->eraseFromParent();
}
return op0;
}
bool SimplifyTrivialPHIs(BasicBlock *BB) {
bool Changed = false;
SmallVector<Instruction *, 16> Removed;
for (Instruction &I : *BB) {
PHINode *PN = dyn_cast<PHINode>(&I);
if (!PN)
continue;
if (PN->getNumIncomingValues() == 1) {
Value *V = PN->getIncomingValue(0);
PN->replaceAllUsesWith(V);
Removed.push_back(PN);
Changed = true;
}
}
for (Instruction *I : Removed)
I->eraseFromParent();
return Changed;
}
llvm::BasicBlock *GetSwitchSuccessorForCond(llvm::SwitchInst *Switch,
llvm::ConstantInt *Cond) {
for (auto it = Switch->case_begin(), end = Switch->case_end(); it != end;
it++) {
if (it.getCaseValue() == Cond) {
return it.getCaseSuccessor();
break;
}
}
return Switch->getDefaultDest();
}
Value *SelectOnOperation(llvm::Instruction *Inst, unsigned operandIdx) {
Instruction *prototype = Inst;
for (unsigned i = 0; i < prototype->getNumOperands(); i++) {
if (i == operandIdx)
continue;
if (!isa<Constant>(prototype->getOperand(i)))
return nullptr;
}
Value *V = prototype->getOperand(operandIdx);
if (SelectInst *SI = dyn_cast<SelectInst>(V)) {
IRBuilder<> Builder(SI);
Instruction *trueClone = Inst->clone();
trueClone->setOperand(operandIdx, SI->getTrueValue());
Builder.Insert(trueClone);
Instruction *falseClone = Inst->clone();
falseClone->setOperand(operandIdx, SI->getFalseValue());
Builder.Insert(falseClone);
Value *newSel =
Builder.CreateSelect(SI->getCondition(), trueClone, falseClone);
return newSel;
}
if (PHINode *Phi = dyn_cast<PHINode>(V)) {
Type *Ty = Inst->getType();
unsigned numOperands = Phi->getNumOperands();
IRBuilder<> Builder(Phi);
PHINode *newPhi = Builder.CreatePHI(Ty, numOperands);
for (unsigned i = 0; i < numOperands; i++) {
BasicBlock *b = Phi->getIncomingBlock(i);
Value *V = Phi->getIncomingValue(i);
Instruction *iClone = Inst->clone();
IRBuilder<> iBuilder(b->getTerminator()->getPrevNode());
iClone->setOperand(operandIdx, V);
iBuilder.Insert(iClone);
newPhi->addIncoming(iClone, b);
}
return newPhi;
}
return nullptr;
}
llvm::Instruction *SkipAllocas(llvm::Instruction *I) {
// Step past any allocas:
while (I && (isa<AllocaInst>(I) || isa<DbgInfoIntrinsic>(I)))
I = I->getNextNode();
return I;
}
llvm::Instruction *FindAllocaInsertionPt(llvm::BasicBlock *BB) {
return &*BB->getFirstInsertionPt();
}
llvm::Instruction *FindAllocaInsertionPt(llvm::Function *F) {
return FindAllocaInsertionPt(&F->getEntryBlock());
}
llvm::Instruction *FindAllocaInsertionPt(llvm::Instruction *I) {
Function *F = I->getParent()->getParent();
if (F)
return FindAllocaInsertionPt(F);
else // BB with no parent function
return FindAllocaInsertionPt(I->getParent());
}
llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Instruction *I) {
return SkipAllocas(FindAllocaInsertionPt(I));
}
llvm::Instruction *FirstNonAllocaInsertionPt(llvm::BasicBlock *BB) {
return SkipAllocas(FindAllocaInsertionPt(BB));
}
llvm::Instruction *FirstNonAllocaInsertionPt(llvm::Function *F) {
return SkipAllocas(FindAllocaInsertionPt(F));
}
static bool ConsumePrefix(StringRef &Str, StringRef Prefix) {
if (!Str.startswith(Prefix))
return false;
Str = Str.substr(Prefix.size());
return true;
}
bool IsResourceSingleComponent(Type *Ty) {
if (llvm::ArrayType *arrType = llvm::dyn_cast<llvm::ArrayType>(Ty)) {
if (arrType->getArrayNumElements() > 1) {
return false;
}
return IsResourceSingleComponent(arrType->getArrayElementType());
} else if (llvm::StructType *structType =
llvm::dyn_cast<llvm::StructType>(Ty)) {
if (structType->getStructNumElements() > 1) {
return false;
}
return IsResourceSingleComponent(structType->getStructElementType(0));
} else if (llvm::FixedVectorType *vectorType =
llvm::dyn_cast<llvm::FixedVectorType>(Ty)) {
if (vectorType->getNumElements() > 1) {
return false;
}
return IsResourceSingleComponent(vectorType->getElementType());
}
return true;
}
uint8_t GetResourceComponentCount(llvm::Type *Ty) {
if (llvm::ArrayType *arrType = llvm::dyn_cast<llvm::ArrayType>(Ty)) {
return arrType->getArrayNumElements() *
GetResourceComponentCount(arrType->getArrayElementType());
} else if (llvm::StructType *structType =
llvm::dyn_cast<llvm::StructType>(Ty)) {
uint32_t Count = 0;
for (Type *EltTy : structType->elements()) {
Count += GetResourceComponentCount(EltTy);
}
DXASSERT(Count <= 4, "Component Count out of bound.");
return Count;
} else if (llvm::FixedVectorType *vectorType =
llvm::dyn_cast<llvm::FixedVectorType>(Ty)) {
return vectorType->getNumElements();
}
return 1;
}
bool IsHLSLResourceType(llvm::Type *Ty) {
return GetHLSLResourceProperties(Ty).first;
}
static DxilResourceProperties
MakeResourceProperties(hlsl::DXIL::ResourceKind Kind, bool UAV, bool ROV,
bool Cmp) {
DxilResourceProperties Ret = {};
Ret.Basic.IsROV = ROV;
Ret.Basic.SamplerCmpOrHasCounter = Cmp;
Ret.Basic.IsUAV = UAV;
Ret.Basic.ResourceKind = (uint8_t)Kind;
return Ret;
}
std::pair<bool, DxilResourceProperties>
GetHLSLResourceProperties(llvm::Type *Ty) {
using RetType = std::pair<bool, DxilResourceProperties>;
RetType FalseRet(false, DxilResourceProperties{});
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return FalseRet;
StringRef name = ST->getName();
ConsumePrefix(name, "class.");
ConsumePrefix(name, "struct.");
if (name == "SamplerState")
return RetType(true,
MakeResourceProperties(hlsl::DXIL::ResourceKind::Sampler,
false, false, false));
if (name == "SamplerComparisonState")
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::Sampler, false,
false, /*cmp or counter*/ true));
if (name.startswith("AppendStructuredBuffer<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::StructuredBuffer,
false, false, /*cmp or counter*/ true));
if (name.startswith("ConsumeStructuredBuffer<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::StructuredBuffer,
false, false, /*cmp or counter*/ true));
if (name == "RaytracingAccelerationStructure")
return RetType(true,
MakeResourceProperties(
hlsl::DXIL::ResourceKind::RTAccelerationStructure,
false, false, false));
if (name.startswith("ConstantBuffer<"))
return RetType(true,
MakeResourceProperties(hlsl::DXIL::ResourceKind::CBuffer,
false, false, false));
if (name.startswith("TextureBuffer<"))
return RetType(true,
MakeResourceProperties(hlsl::DXIL::ResourceKind::TBuffer,
false, false, false));
if (ConsumePrefix(name, "FeedbackTexture2D")) {
hlsl::DXIL::ResourceKind kind = hlsl::DXIL::ResourceKind::Invalid;
if (ConsumePrefix(name, "Array"))
kind = hlsl::DXIL::ResourceKind::FeedbackTexture2DArray;
else
kind = hlsl::DXIL::ResourceKind::FeedbackTexture2D;
if (name.startswith("<"))
return RetType(true, MakeResourceProperties(kind, false, false, false));
return FalseRet;
}
bool ROV = ConsumePrefix(name, "RasterizerOrdered");
bool UAV = ConsumePrefix(name, "RW");
if (name == "ByteAddressBuffer")
return RetType(true,
MakeResourceProperties(hlsl::DXIL::ResourceKind::RawBuffer,
UAV, ROV, false));
if (name.startswith("Buffer<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::TypedBuffer,
UAV, ROV, false));
if (name.startswith("StructuredBuffer<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::StructuredBuffer, UAV,
ROV, false));
if (ConsumePrefix(name, "Texture")) {
if (name.startswith("1D<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::Texture1D,
UAV, ROV, false));
if (name.startswith("1DArray<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::Texture1DArray, UAV,
ROV, false));
if (name.startswith("2D<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::Texture2D,
UAV, ROV, false));
if (name.startswith("2DArray<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::Texture2DArray, UAV,
ROV, false));
if (name.startswith("3D<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::Texture3D,
UAV, ROV, false));
if (name.startswith("Cube<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::TextureCube,
UAV, ROV, false));
if (name.startswith("CubeArray<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::TextureCubeArray,
UAV, ROV, false));
if (name.startswith("2DMS<"))
return RetType(
true, MakeResourceProperties(hlsl::DXIL::ResourceKind::Texture2DMS,
UAV, ROV, false));
if (name.startswith("2DMSArray<"))
return RetType(true, MakeResourceProperties(
hlsl::DXIL::ResourceKind::Texture2DMSArray,
UAV, ROV, false));
return FalseRet;
}
}
return FalseRet;
}
bool IsHLSLObjectType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName()) {
return false;
}
StringRef name = ST->getName();
// TODO: don't check names.
if (name.startswith("dx.types.wave_t"))
return true;
if (name.compare("dx.types.Handle") == 0)
return true;
if (name.endswith("_slice_type"))
return false;
if (IsHLSLResourceType(Ty))
return true;
ConsumePrefix(name, "class.");
ConsumePrefix(name, "struct.");
if (name.startswith("TriangleStream<"))
return true;
if (name.startswith("PointStream<"))
return true;
if (name.startswith("LineStream<"))
return true;
if (IsHLSLWaveMatrixType(Ty))
return true;
if (IsHLSLNodeIOType(Ty))
return true;
}
return false;
}
bool IsHLSLRayQueryType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
// TODO: don't check names.
ConsumePrefix(name, "class.");
if (name.startswith("RayQuery<"))
return true;
}
return false;
}
bool IsHLSLWaveMatrixType(llvm::Type *Ty, DXIL::WaveMatrixKind *pKind) {
if (Ty->isPointerTy())
Ty = Ty->getPointerElementType();
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
// TODO: don't check names.
ConsumePrefix(name, "class.");
if (!ConsumePrefix(name, "WaveMatrix"))
return false;
DXIL::WaveMatrixKind kind = DXIL::WaveMatrixKind::NumKinds;
if (name.startswith("Left<"))
kind = DXIL::WaveMatrixKind::Left;
if (name.startswith("Right<"))
kind = DXIL::WaveMatrixKind::Right;
if (name.startswith("LeftColAcc<"))
kind = DXIL::WaveMatrixKind::LeftColAcc;
if (name.startswith("RightRowAcc<"))
kind = DXIL::WaveMatrixKind::RightRowAcc;
if (name.startswith("Accumulator<"))
kind = DXIL::WaveMatrixKind::Accumulator;
if (pKind)
*pKind = kind;
if (kind != DXIL::WaveMatrixKind::NumKinds)
return true;
}
return false;
}
bool IsHLSLResourceDescType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name == (".Resource"))
return true;
if (name == ".Sampler")
return true;
}
return false;
}
bool IsHLSLNodeOutputType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.startswith("NodeOutput<") || name.equals("EmptyNodeOutput"))
return true;
}
return false;
}
bool IsHLSLNodeOutputArrayType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.startswith("NodeOutputArray<") ||
name.equals("EmptyNodeOutputArray"))
return true;
}
return false;
}
bool IsHLSLEmptyNodeOutputType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.equals("EmptyNodeOutput"))
return true;
}
return false;
}
bool IsHLSLEmptyNodeOutputArrayType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.equals("EmptyNodeOutputArray"))
return true;
}
return false;
}
bool IsHLSLNodeInputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.startswith("DispatchNodeInputRecord<") ||
name.startswith("RWDispatchNodeInputRecord<") ||
name.startswith("GroupNodeInputRecords<") ||
name.startswith("RWGroupNodeInputRecords<") ||
name.startswith("ThreadNodeInputRecord<") ||
name.startswith("RWThreadNodeInputRecord<") ||
name.equals("EmptyNodeInput"))
return true;
}
return false;
}
bool IsHLSLNodeEmptyInputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.equals("EmptyNodeInput"))
return true;
}
return false;
}
bool IsHLSLNodeEmptyOutputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.equals("EmptyNodeOutput"))
return true;
}
return false;
}
bool IsHLSLRWNodeInputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
if (name.startswith("RWDispatchNodeInputRecord<") ||
name.startswith("RWGroupNodeInputRecords<") ||
name.startswith("RWThreadNodeInputRecord<"))
return true;
}
return false;
}
bool IsHLSLNodeOutputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
ConsumePrefix(name, "struct.");
// TODO: don't check names.
if (name.startswith("GroupNodeOutputRecords<") ||
name.startswith("ThreadNodeOutputRecords<"))
return true;
}
return false;
}
bool IsHLSLGSNodeOutputRecordType(llvm::Type *Ty) {
if (llvm::StructType *ST = dyn_cast<llvm::StructType>(Ty)) {
if (!ST->hasName())
return false;
StringRef name = ST->getName();
if (name.startswith("struct.GroupNodeOutputRecords<"))
return true;
}
return false;
}
bool IsHLSLNodeRecordType(llvm::Type *Ty) {
return IsHLSLNodeOutputRecordType(Ty) || IsHLSLNodeInputRecordType(Ty);
}
bool IsHLSLNodeIOType(llvm::Type *Ty) {
return IsHLSLNodeRecordType(Ty) || IsHLSLNodeOutputType(Ty) ||
IsHLSLNodeOutputArrayType(Ty);
}
bool IsIntegerOrFloatingPointType(llvm::Type *Ty) {
return Ty->isIntegerTy() || Ty->isFloatingPointTy();
}
bool ContainsHLSLObjectType(llvm::Type *Ty) {
// Unwrap pointer/array
while (llvm::isa<llvm::PointerType>(Ty))
Ty = llvm::cast<llvm::PointerType>(Ty)->getPointerElementType();
while (llvm::isa<llvm::ArrayType>(Ty))
Ty = llvm::cast<llvm::ArrayType>(Ty)->getArrayElementType();
if (llvm::StructType *ST = llvm::dyn_cast<llvm::StructType>(Ty)) {
if (ST->hasName() && ST->getName().startswith("dx.types."))
return true;
// TODO: How is this suppoed to check for Input/OutputPatch types if
// these have already been eliminated in function arguments during CG?
if (IsHLSLObjectType(Ty))
return true;
// Otherwise, recurse elements of UDT
for (auto ETy : ST->elements()) {
if (ContainsHLSLObjectType(ETy))
return true;
}
}
return false;
}
// Based on the implementation available in LLVM's trunk:
// http://llvm.org/doxygen/Constants_8cpp_source.html#l02734
bool IsSplat(llvm::ConstantDataVector *cdv) {
const char *Base = cdv->getRawDataValues().data();
// Compare elements 1+ to the 0'th element.
unsigned EltSize = cdv->getElementByteSize();
for (unsigned i = 1, e = cdv->getNumElements(); i != e; ++i)
if (memcmp(Base, Base + i * EltSize, EltSize))
return false;
return true;
}
llvm::Type *
StripArrayTypes(llvm::Type *Ty,
llvm::SmallVectorImpl<unsigned> *OuterToInnerLengths) {
DXASSERT_NOMSG(Ty);
while (Ty->isArrayTy()) {
if (OuterToInnerLengths) {
OuterToInnerLengths->push_back(Ty->getArrayNumElements());
}
Ty = Ty->getArrayElementType();
}
return Ty;
}
llvm::Type *WrapInArrayTypes(llvm::Type *Ty,
llvm::ArrayRef<unsigned> OuterToInnerLengths) {
DXASSERT_NOMSG(Ty);
for (auto it = OuterToInnerLengths.rbegin(), E = OuterToInnerLengths.rend();
it != E; ++it) {
Ty = ArrayType::get(Ty, *it);
}
return Ty;
}
llvm::Value *MirrorGEP(llvm::GEPOperator *GEP, llvm::Value *NewBasePtr) {
IRBuilder<> Builder(GEP->getContext());
if (GetElementPtrInst *GEPI = dyn_cast<GetElementPtrInst>(GEP))
Builder.SetInsertPoint(GEPI);
SmallVector<Value *, 4> idxList(GEP->idx_begin(), GEP->idx_end());
return Builder.CreateGEP(NewBasePtr, idxList);
}
namespace {
// Create { v0, v1 } from { v0.lo, v0.hi, v1.lo, v1.hi }
void Make64bitResultForLoad(Type *EltTy, ArrayRef<Value *> resultElts32,
unsigned size, MutableArrayRef<Value *> resultElts,
hlsl::OP *hlslOP, IRBuilder<> &Builder) {
Type *i64Ty = Builder.getInt64Ty();
Type *doubleTy = Builder.getDoubleTy();
if (EltTy == doubleTy) {
Function *makeDouble =
hlslOP->GetOpFunc(DXIL::OpCode::MakeDouble, doubleTy);
Value *makeDoubleOpArg =
Builder.getInt32((unsigned)DXIL::OpCode::MakeDouble);
for (unsigned i = 0; i < size; i++) {
Value *lo = resultElts32[2 * i];
Value *hi = resultElts32[2 * i + 1];
Value *V = Builder.CreateCall(makeDouble, {makeDoubleOpArg, lo, hi});
resultElts[i] = V;
}
} else {
for (unsigned i = 0; i < size; i++) {
Value *lo = resultElts32[2 * i];
Value *hi = resultElts32[2 * i + 1];
lo = Builder.CreateZExt(lo, i64Ty);
hi = Builder.CreateZExt(hi, i64Ty);
hi = Builder.CreateShl(hi, 32);
resultElts[i] = Builder.CreateOr(lo, hi);
}
}
}
// Split { v0, v1 } to { v0.lo, v0.hi, v1.lo, v1.hi }
void Split64bitValForStore(Type *EltTy, ArrayRef<Value *> vals, unsigned size,
MutableArrayRef<Value *> vals32, hlsl::OP *hlslOP,
IRBuilder<> &Builder) {
Type *i32Ty = Builder.getInt32Ty();
Type *doubleTy = Builder.getDoubleTy();
Value *undefI32 = UndefValue::get(i32Ty);
if (EltTy == doubleTy) {
Function *dToU = hlslOP->GetOpFunc(DXIL::OpCode::SplitDouble, doubleTy);
Value *dToUOpArg = Builder.getInt32((unsigned)DXIL::OpCode::SplitDouble);
for (unsigned i = 0; i < size; i++) {
if (isa<UndefValue>(vals[i])) {
vals32[2 * i] = undefI32;
vals32[2 * i + 1] = undefI32;
} else {
Value *retVal = Builder.CreateCall(dToU, {dToUOpArg, vals[i]});
Value *lo = Builder.CreateExtractValue(retVal, 0);
Value *hi = Builder.CreateExtractValue(retVal, 1);
vals32[2 * i] = lo;
vals32[2 * i + 1] = hi;
}
}
} else {
for (unsigned i = 0; i < size; i++) {
if (isa<UndefValue>(vals[i])) {
vals32[2 * i] = undefI32;
vals32[2 * i + 1] = undefI32;
} else {
Value *lo = Builder.CreateTrunc(vals[i], i32Ty);
Value *hi = Builder.CreateLShr(vals[i], 32);
hi = Builder.CreateTrunc(hi, i32Ty);
vals32[2 * i] = lo;
vals32[2 * i + 1] = hi;
}
}
}
}
} // namespace
llvm::CallInst *TranslateCallRawBufferLoadToBufferLoad(
llvm::CallInst *CI, llvm::Function *newFunction, hlsl::OP *op) {
IRBuilder<> Builder(CI);
SmallVector<Value *, 4> args;
args.emplace_back(op->GetI32Const((unsigned)DXIL::OpCode::BufferLoad));
for (unsigned i = 1; i < 4; ++i) {
args.emplace_back(CI->getArgOperand(i));
}
CallInst *newCall = Builder.CreateCall(newFunction, args);
return newCall;
}
void ReplaceRawBufferLoadWithBufferLoad(llvm::Function *F, hlsl::OP *op) {
Type *RTy = F->getReturnType();
if (StructType *STy = dyn_cast<StructType>(RTy)) {
Type *ETy = STy->getElementType(0);
Function *newFunction = op->GetOpFunc(hlsl::DXIL::OpCode::BufferLoad, ETy);
for (auto U = F->user_begin(), E = F->user_end(); U != E;) {
User *user = *(U++);
if (CallInst *CI = dyn_cast<CallInst>(user)) {
CallInst *newCall =
TranslateCallRawBufferLoadToBufferLoad(CI, newFunction, op);
CI->replaceAllUsesWith(newCall);
CI->eraseFromParent();
} else {
DXASSERT(false, "function can only be used with call instructions.");
}
}
} else {
DXASSERT(false, "RawBufferLoad should return struct type.");
}
}
llvm::CallInst *TranslateCallRawBufferStoreToBufferStore(
llvm::CallInst *CI, llvm::Function *newFunction, hlsl::OP *op) {
IRBuilder<> Builder(CI);
SmallVector<Value *, 4> args;
args.emplace_back(op->GetI32Const((unsigned)DXIL::OpCode::BufferStore));
for (unsigned i = 1; i < 9; ++i) {
args.emplace_back(CI->getArgOperand(i));
}
CallInst *newCall = Builder.CreateCall(newFunction, args);
return newCall;
}
void ReplaceRawBufferStoreWithBufferStore(llvm::Function *F, hlsl::OP *op) {
DXASSERT(F->getReturnType()->isVoidTy(),
"rawBufferStore should return a void type.");
Type *ETy = F->getFunctionType()->getParamType(4); // value
Function *newFunction = op->GetOpFunc(hlsl::DXIL::OpCode::BufferStore, ETy);
for (auto U = F->user_begin(), E = F->user_end(); U != E;) {
User *user = *(U++);
if (CallInst *CI = dyn_cast<CallInst>(user)) {
TranslateCallRawBufferStoreToBufferStore(CI, newFunction, op);
CI->eraseFromParent();
} else {
DXASSERT(false, "function can only be used with call instructions.");
}
}
}
void ReplaceRawBufferLoad64Bit(llvm::Function *F, llvm::Type *EltTy,
hlsl::OP *hlslOP) {
Function *bufLd = hlslOP->GetOpFunc(DXIL::OpCode::RawBufferLoad,
Type::getInt32Ty(hlslOP->GetCtx()));
for (auto U = F->user_begin(), E = F->user_end(); U != E;) {
User *user = *(U++);
if (CallInst *CI = dyn_cast<CallInst>(user)) {
IRBuilder<> Builder(CI);
SmallVector<Value *, 4> args(CI->arg_operands());
Value *offset = CI->getArgOperand(
DXIL::OperandIndex::kRawBufferLoadElementOffsetOpIdx);
unsigned size = 0;
bool bNeedStatus = false;
for (User *U : CI->users()) {
ExtractValueInst *Elt = cast<ExtractValueInst>(U);
DXASSERT(Elt->getNumIndices() == 1, "else invalid use for resRet");
unsigned idx = Elt->getIndices()[0];
if (idx == 4) {
bNeedStatus = true;
} else {
size = std::max(size, idx + 1);
}
}
unsigned maskHi = 0;
unsigned maskLo = 0;
switch (size) {
case 1:
maskLo = 3;
break;
case 2:
maskLo = 0xf;
break;
case 3:
maskLo = 0xf;
maskHi = 3;
break;
case 4:
maskLo = 0xf;
maskHi = 0xf;
break;
}
args[DXIL::OperandIndex::kRawBufferLoadMaskOpIdx] =
Builder.getInt8(maskLo);
Value *resultElts[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
CallInst *newLd = Builder.CreateCall(bufLd, args);
Value *resultElts32[8];
unsigned eltBase = 0;
for (unsigned i = 0; i < size; i++) {
if (i == 2) {
// Update offset 4 by 4 bytes.
if (isa<UndefValue>(offset)) {
// [RW]ByteAddressBuffer has undef element offset -> update index
Value *index =
CI->getArgOperand(DXIL::OperandIndex::kRawBufferLoadIndexOpIdx);
args[DXIL::OperandIndex::kRawBufferLoadIndexOpIdx] =
Builder.CreateAdd(index, Builder.getInt32(4 * 4));
} else {
// [RW]StructuredBuffer -> update element offset
args[DXIL::OperandIndex::kRawBufferLoadElementOffsetOpIdx] =
Builder.CreateAdd(offset, Builder.getInt32(4 * 4));
}
args[DXIL::OperandIndex::kRawBufferLoadMaskOpIdx] =
Builder.getInt8(maskHi);
newLd = Builder.CreateCall(bufLd, args);
eltBase = 4;
}
unsigned resBase = 2 * i;
resultElts32[resBase] =
Builder.CreateExtractValue(newLd, resBase - eltBase);
resultElts32[resBase + 1] =
Builder.CreateExtractValue(newLd, resBase + 1 - eltBase);
}
Make64bitResultForLoad(EltTy, resultElts32, size, resultElts, hlslOP,
Builder);
if (bNeedStatus) {
resultElts[4] = Builder.CreateExtractValue(newLd, 4);
}
for (auto it = CI->user_begin(); it != CI->user_end();) {
ExtractValueInst *Elt = cast<ExtractValueInst>(*(it++));
DXASSERT(Elt->getNumIndices() == 1, "else invalid use for resRet");
unsigned idx = Elt->getIndices()[0];
if (!Elt->user_empty()) {
Value *newElt = resultElts[idx];
Elt->replaceAllUsesWith(newElt);
}
Elt->eraseFromParent();
}
CI->eraseFromParent();
} else {
DXASSERT(false, "function can only be used with call instructions.");
}
}
}
void ReplaceRawBufferStore64Bit(llvm::Function *F, llvm::Type *ETy,
hlsl::OP *hlslOP) {
Function *newFunction = hlslOP->GetOpFunc(hlsl::DXIL::OpCode::RawBufferStore,
Type::getInt32Ty(hlslOP->GetCtx()));
for (auto U = F->user_begin(), E = F->user_end(); U != E;) {
User *user = *(U++);
if (CallInst *CI = dyn_cast<CallInst>(user)) {
IRBuilder<> Builder(CI);
SmallVector<Value *, 4> args(CI->arg_operands());
Value *vals[4] = {
CI->getArgOperand(DXIL::OperandIndex::kRawBufferStoreVal0OpIdx),
CI->getArgOperand(DXIL::OperandIndex::kRawBufferStoreVal1OpIdx),
CI->getArgOperand(DXIL::OperandIndex::kRawBufferStoreVal2OpIdx),
CI->getArgOperand(DXIL::OperandIndex::kRawBufferStoreVal3OpIdx)};
ConstantInt *cMask = cast<ConstantInt>(
CI->getArgOperand(DXIL::OperandIndex::kRawBufferStoreMaskOpIdx));
Value *undefI32 = UndefValue::get(Builder.getInt32Ty());
Value *vals32[8] = {undefI32, undefI32, undefI32, undefI32,
undefI32, undefI32, undefI32, undefI32};
unsigned maskLo = 0;
unsigned maskHi = 0;
unsigned size = 0;
unsigned mask = cMask->getLimitedValue();
switch (mask) {
case 1:
maskLo = 3;
size = 1;
break;
case 3:
maskLo = 15;
size = 2;
break;
case 7:
maskLo = 15;
maskHi = 3;
size = 3;
break;
case 15:
maskLo = 15;
maskHi = 15;
size = 4;
break;
default:
DXASSERT(0, "invalid mask");
}
Split64bitValForStore(ETy, vals, size, vals32, hlslOP, Builder);
args[DXIL::OperandIndex::kRawBufferStoreMaskOpIdx] =
Builder.getInt8(maskLo);
args[DXIL::OperandIndex::kRawBufferStoreVal0OpIdx] = vals32[0];
args[DXIL::OperandIndex::kRawBufferStoreVal1OpIdx] = vals32[1];
args[DXIL::OperandIndex::kRawBufferStoreVal2OpIdx] = vals32[2];
args[DXIL::OperandIndex::kRawBufferStoreVal3OpIdx] = vals32[3];
Builder.CreateCall(newFunction, args);
if (maskHi) {
// Update offset 4 by 4 bytes.
Value *offset = args[DXIL::OperandIndex::kBufferStoreCoord1OpIdx];
if (isa<UndefValue>(offset)) {
// [RW]ByteAddressBuffer has element offset == undef -> update index
// instead
Value *index = args[DXIL::OperandIndex::kBufferStoreCoord0OpIdx];
index = Builder.CreateAdd(index, Builder.getInt32(4 * 4));
args[DXIL::OperandIndex::kRawBufferStoreIndexOpIdx] = index;
} else {
// [RW]StructuredBuffer -> update element offset
offset = Builder.CreateAdd(offset, Builder.getInt32(4 * 4));
args[DXIL::OperandIndex::kRawBufferStoreElementOffsetOpIdx] = offset;
}
args[DXIL::OperandIndex::kRawBufferStoreMaskOpIdx] =
Builder.getInt8(maskHi);
args[DXIL::OperandIndex::kRawBufferStoreVal0OpIdx] = vals32[4];
args[DXIL::OperandIndex::kRawBufferStoreVal1OpIdx] = vals32[5];
args[DXIL::OperandIndex::kRawBufferStoreVal2OpIdx] = vals32[6];
args[DXIL::OperandIndex::kRawBufferStoreVal3OpIdx] = vals32[7];
Builder.CreateCall(newFunction, args);
}
CI->eraseFromParent();
} else {
DXASSERT(false, "function can only be used with call instructions.");
}
}
}
bool IsConvergentMarker(const char *Name) {
StringRef RName = Name;
return RName.startswith(kConvergentFunctionPrefix);
}
bool IsConvergentMarker(const Function *F) {
return F && F->getName().startswith(kConvergentFunctionPrefix);
}
bool IsConvergentMarker(Value *V) {
CallInst *CI = dyn_cast<CallInst>(V);
if (!CI)
return false;
return IsConvergentMarker(CI->getCalledFunction());
}
Value *GetConvergentSource(Value *V) {
return cast<CallInst>(V)->getOperand(0);
}
bool isCompositeType(Type *Ty) {
return isa<ArrayType>(Ty) || isa<StructType>(Ty) || isa<VectorType>(Ty);
}
/// If value is a bitcast to base class pattern, equivalent
/// to a getelementptr X, 0, 0, 0... turn it into the appropriate gep.
/// This can enhance SROA and other transforms that want type-safe pointers,
/// and enables merging with other getelementptr's.
Value *TryReplaceBaseCastWithGep(Value *V) {
if (BitCastOperator *BCO = dyn_cast<BitCastOperator>(V)) {
if (!BCO->getSrcTy()->isPointerTy())
return nullptr;
Type *SrcElTy = BCO->getSrcTy()->getPointerElementType();
Type *DstElTy = BCO->getDestTy()->getPointerElementType();
// Adapted from code in InstCombiner::visitBitCast
unsigned NumZeros = 0;
while (SrcElTy != DstElTy && isCompositeType(SrcElTy) &&
SrcElTy->getNumContainedTypes() /* not "{}" */) {
SrcElTy = SrcElTy->getContainedType(0);
++NumZeros;
}
// If we found a path from the src to dest, create the getelementptr now.
if (SrcElTy == DstElTy) {
IRBuilder<> Builder(BCO->getContext());
StringRef Name = "";
if (Instruction *I = dyn_cast<Instruction>(BCO)) {
Builder.SetInsertPoint(I);
Name = I->getName();
}
SmallVector<Value *, 8> Indices(NumZeros + 1, Builder.getInt32(0));
Value *newGEP =
Builder.CreateInBoundsGEP(nullptr, BCO->getOperand(0), Indices, Name);
V->replaceAllUsesWith(newGEP);
if (auto *I = dyn_cast<Instruction>(V))
I->eraseFromParent();
return newGEP;
}
}
return nullptr;
}
// returns true if the function call is an intermediate or DXIL op
// that has no side effects, for functions not marked ReadNone or ReadOnly.
bool FunctionHasNoSideEffects(Instruction *I) {
if (CallInst *CI = dyn_cast<CallInst>(I)) {
// don't force unused convergent markers to stay,
if (hlsl::dxilutil::IsConvergentMarker(CI))
return true;
if (CI->onlyReadsMemory())
return false;
if (!hlsl::OP::IsDxilOpFunc(CI->getCalledFunction()))
return false;
switch (hlsl::OP::getOpCode(I)) {
// remove bad OutputCompletes
case hlsl::OP::OpCode::OutputComplete: {
hlsl::DxilInst_OutputComplete OutputComplete(CI);
Value *NodeRecHandle = OutputComplete.get_output();
Constant *C = dyn_cast<Constant>(NodeRecHandle);
if (C && C->isZeroValue())
return true;
break;
}
// TODO: Add Wave Ops
default:
return false;
}
}
return false;
}
// Calculate Offset
Value *GEPIdxToOffset(GetElementPtrInst *GEP, IRBuilder<> &Builder,
hlsl::OP *OP, const DataLayout &DL) {
SmallVector<Value *, 8> Indices(GEP->idx_begin(), GEP->idx_end());
Value *addr = nullptr;
// update offset
if (GEP->hasAllConstantIndices()) {
unsigned gepOffset =
DL.getIndexedOffset(GEP->getPointerOperandType(), Indices);
addr = OP->GetU32Const(gepOffset);
} else {
Value *offset = OP->GetU32Const(0);
gep_type_iterator GEPIt = gep_type_begin(GEP), E = gep_type_end(GEP);
for (; GEPIt != E; GEPIt++) {
Value *idx = GEPIt.getOperand();
unsigned immIdx = 0;
if (llvm::Constant *constIdx = dyn_cast<llvm::Constant>(idx)) {
immIdx = constIdx->getUniqueInteger().getLimitedValue();
if (immIdx == 0)
continue;
}
if (GEPIt->isPointerTy() || GEPIt->isArrayTy() || GEPIt->isVectorTy()) {
unsigned size = DL.getTypeAllocSize(GEPIt->getSequentialElementType());
if (immIdx) {
unsigned tempOffset = size * immIdx;
offset = Builder.CreateAdd(offset, OP->GetU32Const(tempOffset));
} else {
Value *tempOffset = Builder.CreateMul(idx, OP->GetU32Const(size));
offset = Builder.CreateAdd(offset, tempOffset);
}
} else if (GEPIt->isStructTy()) {
const StructLayout *Layout =
DL.getStructLayout(cast<StructType>(*GEPIt));
unsigned structOffset = Layout->getElementOffset(immIdx);
offset = Builder.CreateAdd(offset, OP->GetU32Const(structOffset));
} else {
gep_type_iterator temp = GEPIt;
temp++;
DXASSERT(temp == E, "scalar type must be the last");
}
}
addr = offset;
}
return addr;
}
struct AllocaDeleter {
SmallVector<Value *, 10> WorkList;
std::unordered_set<Value *> Seen;
void Add(Value *V) {
if (!Seen.count(V)) {
Seen.insert(V);
WorkList.push_back(V);
}
}
bool TryDeleteUnusedAlloca(AllocaInst *AI) {
Seen.clear();
WorkList.clear();
Add(AI);
while (WorkList.size()) {
Value *V = WorkList.pop_back_val();
// Keep adding users if we encounter one of these.
// None of them imply the alloca is being read.
if (isa<GEPOperator>(V) || isa<BitCastOperator>(V) ||
isa<AllocaInst>(V) || isa<StoreInst>(V)) {
for (User *U : V->users())
Add(U);
} else if (MemCpyInst *MC = dyn_cast<MemCpyInst>(V)) {
// If the memcopy's source is anything we've encountered in the
// seen set, then the alloca is being read.
if (Seen.count(MC->getSource()))
return false;
}
// If it's anything else, we'll assume it's reading the
// alloca. Give up.
else {
return false;
}
}
if (!Seen.size())
return false;
// Delete all the instructions associated with the
// alloca.
for (Value *V : Seen) {
Instruction *I = dyn_cast<Instruction>(V);
if (I) {
I->dropAllReferences();
}
}
for (Value *V : Seen) {
Instruction *I = dyn_cast<Instruction>(V);
if (I) {
I->eraseFromParent();
}
}
return true;
}
};
bool DeleteDeadAllocas(llvm::Function &F) {
if (F.empty())
return false;
AllocaDeleter Deleter;
BasicBlock &Entry = *F.begin();
bool Changed = false;
while (1) {
bool LocalChanged = false;
for (Instruction *it = &Entry.back(); it;) {
AllocaInst *AI = dyn_cast<AllocaInst>(it);
it = it->getPrevNode();
if (!AI)
continue;
LocalChanged |= Deleter.TryDeleteUnusedAlloca(AI);
}
Changed |= LocalChanged;
if (!LocalChanged)
break;
}
return Changed;
}
} // namespace dxilutil
} // namespace hlsl