blob: ed206c355376bed15dc63e86353444a8d1fa59d2 [file] [log] [blame]
///////////////////////////////////////////////////////////////////////////////
// //
// DxilNoops.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. //
// //
// Passes to insert dx.noops() and replace them with llvm.donothing() //
// //
///////////////////////////////////////////////////////////////////////////////
//
// Here is how dx.preserve and dx.noop work.
//
// For example, the following HLSL code:
//
// float foo(float y) {
// float x = 10;
// x = 20;
// x += y;
// return x;
// }
//
// float main() : SV_Target {
// float ret = foo(10);
// return ret;
// }
//
// Ordinarily, it gets lowered as:
//
// dx.op.storeOutput(3.0)
//
// Intermediate steps at "x = 20;", "x += y;", "return x", and
// even the call to "foo()" are lost.
//
// But with with Preserve and Noop:
//
// void call dx.noop() // float ret = foo(10);
// %y = dx.preserve(10.0, 10.0) // argument: y=10
// %x0 = dx.preserve(10.0, 10.0) // float x = 10;
// %x1 = dx.preserve(20.0, %x0) // x = 20;
// %x2 = fadd %x1, %y // x += y;
// void call dx.noop() // return x
// %ret = dx.preserve(%x2, %x2) // ret = returned from foo()
// dx.op.storeOutput(%ret)
//
// All the intermediate transformations are visible and could be
// made inspectable in the debugger.
//
// The reason why dx.preserve takes 2 arguments is so that the previous
// value of a variable does not get cleaned up by DCE. For example:
//
// float x = ...;
// do_some_stuff_with(x);
// do_some_other_stuff(); // At this point, x's last values
// // are dead and register allocators
// // are free to reuse its location during
// // call this code.
// // So until x is assigned a new value below
// // x could become unavailable.
// //
// // The second parameter in dx.preserve
// // keeps x's previous value alive.
//
// x = ...; // Assign something else
//
//
// When emitting proper DXIL, dx.noop and dx.preserve are lowered to
// ordinary LLVM instructions that do not affect the semantic of the
// shader, but can be used by a debugger or backend generator if they
// know what to look for.
//
// We generate two special internal constant global vars:
//
// @dx.preserve.value = internal constant i1 false
// @dx.nothing = internal constant i32 0
//
// "call dx.noop()" is lowered to "load @dx.nothing"
//
// "... = call dx.preserve(%cur_val, %last_val)" is lowered to:
//
// %p = load @dx.preserve.value
// ... = select i1 %p, %last_val, %cur_val
//
// Since %p is guaranteed to be false, the select is guaranteed
// to return %cur_val.
//
#include "dxc/HLSL/DxilNoops.h"
#include "dxc/DXIL/DxilConstants.h"
#include "dxc/DXIL/DxilMetadataHelper.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Analysis/DxilValueCache.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/Local.h"
#include <unordered_set>
using namespace llvm;
static Function *GetOrCreateNoopF(Module &M) {
LLVMContext &Ctx = M.getContext();
FunctionType *FT = FunctionType::get(Type::getVoidTy(Ctx), false);
Function *NoopF = cast<Function>(M.getOrInsertFunction(hlsl::kNoopName, FT));
NoopF->addFnAttr(Attribute::AttrKind::Convergent);
return NoopF;
}
static Constant *GetConstGep(Constant *Ptr, unsigned Idx0, unsigned Idx1) {
Type *i32Ty = Type::getInt32Ty(Ptr->getContext());
Constant *Indices[] = {ConstantInt::get(i32Ty, Idx0),
ConstantInt::get(i32Ty, Idx1)};
return ConstantExpr::getGetElementPtr(nullptr, Ptr, Indices);
}
struct Store_Info {
Instruction *StoreOrMC = nullptr;
Value *Source = nullptr; // Alloca, GV, or Argument
bool AllowLoads = false;
};
static void FindAllStores(Value *Ptr, std::vector<Store_Info> *Stores,
std::vector<Value *> &WorklistStorage,
std::unordered_set<Value *> &SeenStorage) {
assert(isa<Argument>(Ptr) || isa<AllocaInst>(Ptr) ||
isa<GlobalVariable>(Ptr));
WorklistStorage.clear();
WorklistStorage.push_back(Ptr);
// Don't clear Seen Storage because two pointers can be involved with the same
// memcpy. Clearing it can get the memcpy added twice.
unsigned StartIdx = Stores->size();
bool AllowLoad = false;
while (WorklistStorage.size()) {
Value *V = WorklistStorage.back();
WorklistStorage.pop_back();
SeenStorage.insert(V);
if (isa<BitCastOperator>(V) || isa<GEPOperator>(V) ||
isa<GlobalVariable>(V) || isa<AllocaInst>(V) || isa<Argument>(V)) {
for (User *U : V->users()) {
MemCpyInst *MC = nullptr;
// Allow load if MC reads from pointer
if ((MC = dyn_cast<MemCpyInst>(U)) && MC->getSource() == V) {
AllowLoad = true;
} else if (isa<LoadInst>(U)) {
AllowLoad = true;
}
// Add to worklist if we haven't seen it before.
else {
if (!SeenStorage.count(U))
WorklistStorage.push_back(U);
}
}
} else if (StoreInst *Store = dyn_cast<StoreInst>(V)) {
Store_Info Info;
Info.StoreOrMC = Store;
Info.Source = Ptr;
Stores->push_back(Info);
} else if (MemCpyInst *MC = dyn_cast<MemCpyInst>(V)) {
Store_Info Info;
Info.StoreOrMC = MC;
Info.Source = Ptr;
Stores->push_back(Info);
}
}
if (isa<GlobalVariable>(Ptr)) {
AllowLoad = true;
}
if (AllowLoad) {
Store_Info *ptr = Stores->data();
for (unsigned i = StartIdx; i < Stores->size(); i++)
ptr[i].AllowLoads = true;
}
}
static User *GetUniqueUser(Value *V) {
if (V->user_begin() != V->user_end()) {
if (std::next(V->user_begin()) == V->user_end())
return *V->user_begin();
}
return nullptr;
}
static Value *GetOrCreatePreserveCond(Function *F) {
assert(!F->isDeclaration());
Module *M = F->getParent();
GlobalVariable *GV = M->getGlobalVariable(hlsl::kPreserveName, true);
if (!GV) {
Type *i32Ty = Type::getInt32Ty(M->getContext());
Type *i32ArrayTy = ArrayType::get(i32Ty, 1);
unsigned int Values[1] = {0};
Constant *InitialValue =
llvm::ConstantDataArray::get(M->getContext(), Values);
GV = new GlobalVariable(*M, i32ArrayTy, true,
llvm::GlobalValue::InternalLinkage, InitialValue,
hlsl::kPreserveName);
}
for (User *U : GV->users()) {
GEPOperator *Gep = cast<GEPOperator>(U);
for (User *GepU : Gep->users()) {
LoadInst *LI = cast<LoadInst>(GepU);
if (LI->getParent()->getParent() == F) {
return GetUniqueUser(LI);
}
}
}
BasicBlock *BB = &F->getEntryBlock();
Instruction *InsertPt = &BB->front();
while (isa<AllocaInst>(InsertPt) || isa<DbgInfoIntrinsic>(InsertPt))
InsertPt = InsertPt->getNextNode();
IRBuilder<> B(InsertPt);
Constant *Gep = GetConstGep(GV, 0, 0);
LoadInst *Load = B.CreateLoad(Gep);
return B.CreateTrunc(Load, B.getInt1Ty());
}
bool hlsl::IsNop(llvm::Instruction *I) {
CallInst *CI = dyn_cast<CallInst>(I);
if (!CI)
return false;
Function *F = CI->getCalledFunction();
return F && F->getName() == hlsl::kNoopName;
}
static bool IsPreserveLoad(llvm::Instruction *I) {
LoadInst *Load = dyn_cast<LoadInst>(I);
if (!Load)
return false;
GEPOperator *GEP = dyn_cast<GEPOperator>(Load->getPointerOperand());
if (!GEP)
return false;
GlobalVariable *GV = dyn_cast<GlobalVariable>(GEP->getPointerOperand());
return GV &&
GV->getLinkage() == GlobalVariable::LinkageTypes::InternalLinkage &&
GV->getName() == hlsl::kPreserveName;
}
static bool IsPreserveTrunc(llvm::Instruction *I) {
TruncInst *Trunc = dyn_cast<TruncInst>(I);
if (!Trunc)
return false;
Instruction *Load = dyn_cast<Instruction>(Trunc->getOperand(0));
if (!Load)
return false;
return IsPreserveLoad(Load);
}
bool hlsl::IsPreserve(llvm::Instruction *I) {
SelectInst *S = dyn_cast<SelectInst>(I);
if (!S)
return false;
Instruction *Trunc = dyn_cast<Instruction>(S->getCondition());
if (!Trunc)
return false;
return IsPreserveTrunc(Trunc);
}
bool hlsl::IsPreserveRelatedValue(llvm::Instruction *I) {
return IsPreserveLoad(I) || IsPreserveTrunc(I) || hlsl::IsPreserve(I);
}
static Function *GetOrCreatePreserveF(Module *M, Type *Ty) {
std::string str = hlsl::kPreservePrefix;
raw_string_ostream os(str);
Ty->print(os);
os.flush();
FunctionType *FT = FunctionType::get(Ty, {Ty, Ty}, false);
Function *PreserveF = cast<Function>(M->getOrInsertFunction(str, FT));
PreserveF->addFnAttr(Attribute::AttrKind::ReadNone);
PreserveF->addFnAttr(Attribute::AttrKind::NoUnwind);
return PreserveF;
}
static Instruction *CreatePreserve(Value *V, Value *LastV,
Instruction *InsertPt) {
assert(V->getType() == LastV->getType());
Type *Ty = V->getType();
Function *PreserveF = GetOrCreatePreserveF(InsertPt->getModule(), Ty);
return CallInst::Create(PreserveF, ArrayRef<Value *>{V, LastV}, "", InsertPt);
}
static void LowerPreserveToSelect(CallInst *CI) {
Value *V = CI->getArgOperand(0);
Value *LastV = CI->getArgOperand(1);
if (LastV == V)
LastV = UndefValue::get(V->getType());
Value *Cond = GetOrCreatePreserveCond(CI->getParent()->getParent());
SelectInst *Select = SelectInst::Create(Cond, LastV, V, "", CI);
Select->setDebugLoc(CI->getDebugLoc());
CI->replaceAllUsesWith(Select);
CI->eraseFromParent();
}
static void InsertNoopAt(Instruction *I) {
Module &M = *I->getModule();
Function *NoopF = GetOrCreateNoopF(M);
CallInst *Noop = CallInst::Create(NoopF, {}, I);
Noop->setDebugLoc(I->getDebugLoc());
}
static void InsertPreserve(bool AllowLoads, StoreInst *Store) {
Value *V = Store->getValueOperand();
IRBuilder<> B(Store);
Value *Last_Value = nullptr;
// If there's never any loads for this memory location,
// don't generate a load.
if (AllowLoads) {
Last_Value = B.CreateLoad(Store->getPointerOperand());
} else {
Last_Value = UndefValue::get(V->getType());
}
Instruction *Preserve = CreatePreserve(V, Last_Value, Store);
Preserve->setDebugLoc(Store->getDebugLoc());
Store->replaceUsesOfWith(V, Preserve);
}
//==========================================================
// Insertion pass
//
// This pass inserts dx.noop and dx.preserve where we want
// to preserve line mapping or perserve some intermediate
// values.
struct DxilInsertPreserves : public ModulePass {
static char ID;
DxilInsertPreserves(bool AllowPreserves = false)
: ModulePass(ID), AllowPreserves(AllowPreserves) {
initializeDxilInsertPreservesPass(*PassRegistry::getPassRegistry());
}
bool AllowPreserves = false;
// Function overrides that resolve options when used for DxOpt
void applyOptions(PassOptions O) override {
GetPassOptionBool(O, "AllowPreserves", &AllowPreserves, false);
}
void dumpConfig(raw_ostream &OS) override {
ModulePass::dumpConfig(OS);
OS << ",AllowPreserves=" << AllowPreserves;
}
bool runOnModule(Module &M) override {
std::vector<Store_Info> Stores;
std::vector<Value *> WorklistStorage;
std::unordered_set<Value *> SeenStorage;
for (GlobalVariable &GV : M.globals()) {
if (GV.getLinkage() != GlobalValue::LinkageTypes::InternalLinkage ||
GV.getType()->getPointerAddressSpace() ==
hlsl::DXIL::kTGSMAddrSpace) {
continue;
}
for (User *U : GV.users()) {
if (LoadInst *LI = dyn_cast<LoadInst>(U)) {
InsertNoopAt(LI);
}
}
FindAllStores(&GV, &Stores, WorklistStorage, SeenStorage);
}
bool Changed = false;
for (Function &F : M) {
if (F.isDeclaration())
continue;
// Collect Stores on Allocas in function
BasicBlock *Entry = &*F.begin();
for (Instruction &I : *Entry) {
AllocaInst *AI = dyn_cast<AllocaInst>(&I);
if (!AI)
continue;
// Skip temp allocas
if (!AI->getMetadata(hlsl::DxilMDHelper::kDxilTempAllocaMDName))
FindAllStores(AI, &Stores, WorklistStorage, SeenStorage);
}
// Collect Stores on pointer Arguments in function
for (Argument &Arg : F.args()) {
if (Arg.getType()->isPointerTy())
FindAllStores(&Arg, &Stores, WorklistStorage, SeenStorage);
}
// For every real function call, insert a nop
// so we can put a breakpoint there.
for (User *U : F.users()) {
if (CallInst *CI = dyn_cast<CallInst>(U)) {
InsertNoopAt(CI);
}
}
// Insert nops for void return statements
for (BasicBlock &BB : F) {
ReturnInst *Ret = dyn_cast<ReturnInst>(BB.getTerminator());
if (Ret)
InsertNoopAt(Ret);
}
}
// Insert preserves or noops for these stores
for (Store_Info &Info : Stores) {
if (StoreInst *Store = dyn_cast<StoreInst>(Info.StoreOrMC)) {
Value *V = Store->getValueOperand();
if (this->AllowPreserves && V && !V->getType()->isAggregateType() &&
!V->getType()->isPointerTy()) {
InsertPreserve(Info.AllowLoads, Store);
Changed = true;
} else {
InsertNoopAt(Store);
Changed = true;
}
} else if (MemCpyInst *MC = cast<MemCpyInst>(Info.StoreOrMC)) {
// TODO: Do something to preserve pointer's previous value.
InsertNoopAt(MC);
Changed = true;
}
}
return Changed;
}
StringRef getPassName() const override { return "Dxil Insert Preserves"; }
};
char DxilInsertPreserves::ID;
Pass *llvm::createDxilInsertPreservesPass(bool AllowPreserves) {
return new DxilInsertPreserves(AllowPreserves);
}
INITIALIZE_PASS(DxilInsertPreserves, "dxil-insert-preserves",
"Dxil Insert Preserves", false, false)
//==========================================================
// Lower dx.preserve to select
//
// This pass replaces all dx.preserve calls to select
//
namespace {
class DxilPreserveToSelect : public ModulePass {
public:
static char ID;
SmallDenseMap<Type *, Function *> PreserveFunctions;
DxilPreserveToSelect() : ModulePass(ID) {
initializeDxilPreserveToSelectPass(*PassRegistry::getPassRegistry());
}
bool runOnModule(Module &M) override {
bool Changed = false;
for (auto fit = M.getFunctionList().begin(),
end = M.getFunctionList().end();
fit != end;) {
Function *F = &*(fit++);
if (!F->isDeclaration())
continue;
if (F->getName().startswith(hlsl::kPreservePrefix)) {
for (auto uit = F->user_begin(), end = F->user_end(); uit != end;) {
User *U = *(uit++);
CallInst *CI = cast<CallInst>(U);
LowerPreserveToSelect(CI);
}
F->eraseFromParent();
Changed = true;
}
}
return Changed;
}
StringRef getPassName() const override {
return "Dxil Lower Preserves to Selects";
}
};
char DxilPreserveToSelect::ID;
} // namespace
Pass *llvm::createDxilPreserveToSelectPass() {
return new DxilPreserveToSelect();
}
INITIALIZE_PASS(DxilPreserveToSelect, "dxil-preserves-to-select",
"Dxil Preserves To Select", false, false)
//==========================================================
// output Argument debug info rewrite
//
namespace {
class DxilRewriteOutputArgDebugInfo : public ModulePass {
public:
static char ID;
DxilRewriteOutputArgDebugInfo() : ModulePass(ID) {
initializeDxilRewriteOutputArgDebugInfoPass(
*PassRegistry::getPassRegistry());
}
bool runOnModule(Module &M) override {
DITypeIdentifierMap EmptyMap;
DIBuilder DIB(M);
bool Changed = false;
for (Function &F : M) {
for (Argument &Arg : F.args()) {
if (!Arg.getType()->isPointerTy())
continue;
DbgDeclareInst *Declare = llvm::FindAllocaDbgDeclare(&Arg);
if (!Declare)
continue;
DILocalVariable *Var = Declare->getVariable();
DIType *Ty = Var->getType().resolve(EmptyMap);
DIExpression *Expr = Declare->getExpression();
if (Expr->getNumElements() == 1 &&
Expr->getElement(0) == dwarf::DW_OP_deref) {
while (Ty && (Ty->getTag() == dwarf::DW_TAG_reference_type ||
Ty->getTag() == dwarf::DW_TAG_restrict_type)) {
Ty = cast<DIDerivedType>(Ty)->getBaseType().resolve(EmptyMap);
}
if (Ty) {
DILocalVariable *NewVar = DIB.createLocalVariable(
dwarf::DW_TAG_arg_variable, Var->getScope(), Var->getName(),
Var->getFile(), Var->getLine(), Ty, false, 0, Var->getArg());
DIExpression *EmptyExpr = DIExpression::get(M.getContext(), {});
DIB.insertDeclare(&Arg, NewVar, EmptyExpr, Declare->getDebugLoc(),
Declare);
Declare->eraseFromParent();
Changed = true;
}
}
}
}
return Changed;
}
StringRef getPassName() const override {
return "Dxil Rewrite Output Arg Debug Info";
}
};
char DxilRewriteOutputArgDebugInfo::ID;
} // namespace
Pass *llvm::createDxilRewriteOutputArgDebugInfoPass() {
return new DxilRewriteOutputArgDebugInfo();
}
INITIALIZE_PASS(DxilRewriteOutputArgDebugInfo,
"dxil-rewrite-output-arg-debug-info",
"Dxil Rewrite Output Arg Debug Info", false, false)
//==========================================================
// Reader pass
//
namespace {
class DxilReinsertNops : public ModulePass {
public:
static char ID;
DxilReinsertNops() : ModulePass(ID) {
initializeDxilReinsertNopsPass(*PassRegistry::getPassRegistry());
}
// In various linking scenarios, the dx.nothing.a variable might be prefixed
// and/or suffixed with something:
//
// <library_name>.dx.nothing.a.<another_thing>
//
// This routine looks for the "dx.nothing.a" string inside of it, and as long
// as it's used in the expected way:
//
// %0 = load i32, i32* getelementptr inbounds ([1 x i32], [1 x i32]*
// @dx.nothing.a, i32 0, i32 0)
//
// ...it is deemed a valid nop.
//
static bool IsLegalNothingVarName(StringRef Name) {
// There should be a single instance of the name in this GV.
if (1 != Name.count(hlsl::kNothingName))
return false;
size_t Loc = Name.find(hlsl::kNothingName);
StringRef Prefix = Name.substr(0, Loc);
StringRef Suffix = Name.substr(Loc + Name.size());
// There should be either no prefix or a prefix that ends with .
if (!Prefix.empty() && !Prefix.endswith(".")) {
return false;
}
// There should be either no suffix or a prefix that begins with with .
if (!Suffix.empty() && !Suffix.startswith(".")) {
return false;
}
return true;
}
bool runOnModule(Module &M) override {
bool Changed = false;
for (GlobalVariable &GV : M.globals()) {
if (!IsLegalNothingVarName(GV.getName()))
continue;
const bool IsValidType = GV.getValueType()->isArrayTy() &&
GV.getValueType()->getArrayElementType() ==
Type::getInt32Ty(M.getContext()) &&
GV.getValueType()->getArrayNumElements() == 1;
if (!IsValidType)
return false;
for (User *GVU : GV.users()) {
ConstantExpr *CE = dyn_cast<ConstantExpr>(GVU);
if (!CE || CE->getOpcode() != Instruction::GetElementPtr)
continue;
for (auto it = CE->user_begin(), end = CE->user_end(); it != end;) {
User *U = *(it++);
LoadInst *LI = dyn_cast<LoadInst>(U);
if (!LI)
continue;
InsertNoopAt(LI);
LI->eraseFromParent();
Changed = true;
}
}
}
return Changed;
}
StringRef getPassName() const override { return "Dxil Reinsert Nops"; }
};
char DxilReinsertNops::ID;
} // namespace
Pass *llvm::createDxilReinsertNopsPass() { return new DxilReinsertNops(); }
INITIALIZE_PASS(DxilReinsertNops, "dxil-reinsert-nops", "Dxil Reinsert Nops",
false, false)
//==========================================================
// Finalize pass
//
namespace {
class DxilFinalizePreserves : public ModulePass {
public:
static char ID;
GlobalVariable *NothingGV = nullptr;
DxilFinalizePreserves() : ModulePass(ID) {
initializeDxilFinalizePreservesPass(*PassRegistry::getPassRegistry());
}
Instruction *GetFinalNoopInst(Module &M, Instruction *InsertBefore) {
Type *i32Ty = Type::getInt32Ty(M.getContext());
if (!NothingGV) {
NothingGV = M.getGlobalVariable(hlsl::kNothingName);
if (!NothingGV) {
Type *i32ArrayTy = ArrayType::get(i32Ty, 1);
unsigned int Values[1] = {0};
Constant *InitialValue =
llvm::ConstantDataArray::get(M.getContext(), Values);
NothingGV = new GlobalVariable(M, i32ArrayTy, true,
llvm::GlobalValue::InternalLinkage,
InitialValue, hlsl::kNothingName);
}
}
Constant *Gep = GetConstGep(NothingGV, 0, 0);
return new llvm::LoadInst(Gep, nullptr, InsertBefore);
}
bool LowerPreserves(Module &M);
bool LowerNoops(Module &M);
bool runOnModule(Module &M) override;
StringRef getPassName() const override { return "Dxil Finalize Preserves"; }
};
char DxilFinalizePreserves::ID;
} // namespace
// Fix undefs in the dx.preserve -> selects
bool DxilFinalizePreserves::LowerPreserves(Module &M) {
bool Changed = false;
GlobalVariable *GV = M.getGlobalVariable(hlsl::kPreserveName, true);
if (GV) {
for (User *U : GV->users()) {
GEPOperator *Gep = cast<GEPOperator>(U);
for (User *GepU : Gep->users()) {
LoadInst *LI = cast<LoadInst>(GepU);
assert(LI->user_begin() != LI->user_end() &&
std::next(LI->user_begin()) == LI->user_end());
Instruction *I = cast<Instruction>(*LI->user_begin());
for (User *UU : I->users()) {
SelectInst *P = cast<SelectInst>(UU);
Value *PrevV = P->getTrueValue();
Value *CurV = P->getFalseValue();
if (isa<UndefValue>(PrevV) || isa<Constant>(PrevV)) {
P->setOperand(1, CurV);
Changed = true;
}
}
}
}
}
return Changed;
}
// Replace all @dx.noop's with load @dx.nothing.value
bool DxilFinalizePreserves::LowerNoops(Module &M) {
bool Changed = false;
Function *NoopF = nullptr;
for (Function &F : M) {
if (!F.isDeclaration())
continue;
if (F.getName() == hlsl::kNoopName) {
NoopF = &F;
}
}
if (NoopF) {
for (auto It = NoopF->user_begin(), E = NoopF->user_end(); It != E;) {
User *U = *(It++);
CallInst *CI = cast<CallInst>(U);
Instruction *Nop = GetFinalNoopInst(M, CI);
Nop->setDebugLoc(CI->getDebugLoc());
CI->eraseFromParent();
Changed = true;
}
assert(NoopF->user_empty() && "dx.noop calls must be all removed now");
NoopF->eraseFromParent();
}
return Changed;
}
// Replace all preserves and nops
bool DxilFinalizePreserves::runOnModule(Module &M) {
bool Changed = false;
Changed |= LowerPreserves(M);
Changed |= LowerNoops(M);
return Changed;
}
Pass *llvm::createDxilFinalizePreservesPass() {
return new DxilFinalizePreserves();
}
INITIALIZE_PASS(DxilFinalizePreserves, "dxil-finalize-preserves",
"Dxil Finalize Preserves", false, false)