|
@@ -8,28 +8,106 @@
|
|
// Passes to insert dx.noops() and replace them with llvm.donothing() //
|
|
// 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 "llvm/Pass.h"
|
|
#include "llvm/Pass.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/IntrinsicInst.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
|
|
+#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
#include "llvm/Transforms/Scalar.h"
|
|
|
|
+#include "llvm/Support/raw_os_ostream.h"
|
|
|
|
+#include "dxc/DXIL/DxilMetadataHelper.h"
|
|
|
|
+#include "dxc/DXIL/DxilConstants.h"
|
|
|
|
+
|
|
|
|
+#include <unordered_set>
|
|
|
|
|
|
using namespace llvm;
|
|
using namespace llvm;
|
|
|
|
|
|
namespace {
|
|
namespace {
|
|
StringRef kNoopName = "dx.noop";
|
|
StringRef kNoopName = "dx.noop";
|
|
|
|
+StringRef kPreservePrefix = "dx.preserve.";
|
|
StringRef kNothingName = "dx.nothing";
|
|
StringRef kNothingName = "dx.nothing";
|
|
|
|
+StringRef kPreserveName = "dx.preserve.value";
|
|
}
|
|
}
|
|
|
|
|
|
-//==========================================================
|
|
|
|
-// Insertion pass
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
-namespace {
|
|
|
|
-
|
|
|
|
-Function *GetOrCreateNoopF(Module &M) {
|
|
|
|
|
|
+static Function *GetOrCreateNoopF(Module &M) {
|
|
LLVMContext &Ctx = M.getContext();
|
|
LLVMContext &Ctx = M.getContext();
|
|
FunctionType *FT = FunctionType::get(Type::getVoidTy(Ctx), false);
|
|
FunctionType *FT = FunctionType::get(Type::getVoidTy(Ctx), false);
|
|
Function *NoopF = cast<Function>(M.getOrInsertFunction(::kNoopName, FT));
|
|
Function *NoopF = cast<Function>(M.getOrInsertFunction(::kNoopName, FT));
|
|
@@ -37,74 +115,334 @@ Function *GetOrCreateNoopF(Module &M) {
|
|
return NoopF;
|
|
return NoopF;
|
|
}
|
|
}
|
|
|
|
|
|
-class DxilInsertNoops : public FunctionPass {
|
|
|
|
-public:
|
|
|
|
- static char ID;
|
|
|
|
- DxilInsertNoops() : FunctionPass(ID) {
|
|
|
|
- initializeDxilInsertNoopsPass(*PassRegistry::getPassRegistry());
|
|
|
|
|
|
+static bool ShouldPreserve(Value *V) {
|
|
|
|
+ if (isa<Constant>(V)) return true;
|
|
|
|
+ if (isa<Argument>(V)) return true;
|
|
|
|
+ if (isa<LoadInst>(V)) return true;
|
|
|
|
+ if (ExtractElementInst *GEP = dyn_cast<ExtractElementInst>(V)) {
|
|
|
|
+ return ShouldPreserve(GEP->getVectorOperand());
|
|
}
|
|
}
|
|
|
|
+ if (isa<CallInst>(V)) return true;
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
|
|
- bool runOnFunction(Function &F) override;
|
|
|
|
- const char *getPassName() const override { return "Dxil Insert Noops"; }
|
|
|
|
|
|
+struct Store_Info {
|
|
|
|
+ Instruction *StoreOrMC = nullptr;
|
|
|
|
+ Value *Source = nullptr; // Alloca, GV, or Argument
|
|
|
|
+ bool AllowLoads = false;
|
|
};
|
|
};
|
|
|
|
|
|
-char DxilInsertNoops::ID;
|
|
|
|
|
|
+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()) {
|
|
|
|
+ // Allow load if MC reads from pointer
|
|
|
|
+ if (MemCpyInst *MC = dyn_cast<MemCpyInst>(U)) {
|
|
|
|
+ AllowLoad |= MC->getSource() == V;
|
|
|
|
+ }
|
|
|
|
+ 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)) {
|
|
|
|
+ if (ShouldPreserve(Store->getValueOperand())) {
|
|
|
|
+ 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 Value *GetOrCreatePreserveCond(Function *F) {
|
|
|
|
+ assert(!F->isDeclaration());
|
|
|
|
+
|
|
|
|
+ Module *M = F->getParent();
|
|
|
|
+ GlobalVariable *GV = M->getGlobalVariable(kPreserveName, true);
|
|
|
|
+ if (!GV) {
|
|
|
|
+ Type *i32Ty = Type::getInt32Ty(M->getContext());
|
|
|
|
+ GV = new GlobalVariable(*M,
|
|
|
|
+ i32Ty, true,
|
|
|
|
+ llvm::GlobalValue::InternalLinkage,
|
|
|
|
+ llvm::ConstantInt::get(i32Ty, 0), kPreserveName);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (User *U : GV->users()) {
|
|
|
|
+ LoadInst *LI = cast<LoadInst>(U);
|
|
|
|
+ if (LI->getParent()->getParent() == F) {
|
|
|
|
+ assert(LI->user_begin() != LI->user_end() &&
|
|
|
|
+ std::next(LI->user_begin()) == LI->user_end());
|
|
|
|
+
|
|
|
|
+ return *LI->user_begin();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ BasicBlock *BB = &F->getEntryBlock();
|
|
|
|
+ Instruction *InsertPt = &BB->front();
|
|
|
|
+ while (isa<AllocaInst>(InsertPt) || isa<DbgInfoIntrinsic>(InsertPt))
|
|
|
|
+ InsertPt = InsertPt->getNextNode();
|
|
|
|
+
|
|
|
|
+ IRBuilder<> B(InsertPt);
|
|
|
|
+
|
|
|
|
+ LoadInst *Load = B.CreateLoad(GV);
|
|
|
|
+ return B.CreateTrunc(Load, B.getInt1Ty());
|
|
}
|
|
}
|
|
|
|
|
|
-bool DxilInsertNoops::runOnFunction(Function &F) {
|
|
|
|
- Module &M = *F.getParent();
|
|
|
|
- Function *NoopF = nullptr;
|
|
|
|
- bool Changed = false;
|
|
|
|
|
|
|
|
- // Find instructions where we want to insert nops
|
|
|
|
- for (BasicBlock &BB : F) {
|
|
|
|
- for (BasicBlock::iterator It = BB.begin(), E = BB.end(); It != E;) {
|
|
|
|
- bool InsertNop = false;
|
|
|
|
- Instruction &I = *(It++);
|
|
|
|
- // If we are calling a real function, insert one
|
|
|
|
- // at the callsite.
|
|
|
|
- if (CallInst *Call = dyn_cast<CallInst>(&I)) {
|
|
|
|
- if (Function *F = Call->getCalledFunction()) {
|
|
|
|
- if (!F->isDeclaration())
|
|
|
|
- InsertNop = true;
|
|
|
|
|
|
+static Function *GetOrCreatePreserveF(Module *M, Type *Ty) {
|
|
|
|
+ std::string str = 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());
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+//==========================================================
|
|
|
|
+// 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() : ModulePass(ID) {
|
|
|
|
+ initializeDxilInsertPreservesPass(*PassRegistry::getPassRegistry());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- else if (MemCpyInst *MC = dyn_cast<MemCpyInst>(&I)) {
|
|
|
|
- InsertNop = true;
|
|
|
|
|
|
+
|
|
|
|
+ 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);
|
|
}
|
|
}
|
|
- // If we have a copy, e.g:
|
|
|
|
- // float x = 0;
|
|
|
|
- // float y = x; <---- copy
|
|
|
|
- // insert a nop there.
|
|
|
|
- else if (StoreInst *Store = dyn_cast<StoreInst>(&I)) {
|
|
|
|
|
|
+
|
|
|
|
+ // 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();
|
|
Value *V = Store->getValueOperand();
|
|
- if (isa<LoadInst>(V) || isa<Constant>(V))
|
|
|
|
- InsertNop = true;
|
|
|
|
|
|
+
|
|
|
|
+ if (V &&
|
|
|
|
+ !V->getType()->isAggregateType() &&
|
|
|
|
+ !V->getType()->isPointerTy())
|
|
|
|
+ {
|
|
|
|
+ IRBuilder<> B(Store);
|
|
|
|
+ Value *Last_Value = nullptr;
|
|
|
|
+ // If there's never any loads for this memory location,
|
|
|
|
+ // don't generate a load.
|
|
|
|
+ if (Info.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);
|
|
|
|
+
|
|
|
|
+ Changed = true;
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ InsertNoopAt(Store);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- // If we have a return, just to be safe.
|
|
|
|
- else if (ReturnInst *Ret = dyn_cast<ReturnInst>(&I)) {
|
|
|
|
- InsertNop = true;
|
|
|
|
|
|
+ else if (MemCpyInst *MC = cast<MemCpyInst>(Info.StoreOrMC)) {
|
|
|
|
+ // TODO: Do something to preserve pointer's previous value.
|
|
|
|
+ InsertNoopAt(MC);
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Changed;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const char *getPassName() const override { return "Dxil Insert Preserves"; }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+char DxilInsertPreserves::ID;
|
|
|
|
+
|
|
|
|
+Pass *llvm::createDxilInsertPreservesPass() {
|
|
|
|
+ return new DxilInsertPreserves();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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());
|
|
|
|
+ }
|
|
|
|
|
|
- // Do the insertion
|
|
|
|
- if (InsertNop) {
|
|
|
|
- if (!NoopF)
|
|
|
|
- NoopF = GetOrCreateNoopF(M);
|
|
|
|
- CallInst *Noop = CallInst::Create(NoopF, {}, &I);
|
|
|
|
- Noop->setDebugLoc(I.getDebugLoc());
|
|
|
|
|
|
+ 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(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;
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return Changed;
|
|
}
|
|
}
|
|
|
|
+ const char *getPassName() const override { return "Dxil Lower Preserves to Selects"; }
|
|
|
|
+};
|
|
|
|
|
|
- return Changed;
|
|
|
|
|
|
+char DxilPreserveToSelect::ID;
|
|
}
|
|
}
|
|
|
|
|
|
-Pass *llvm::createDxilInsertNoopsPass() {
|
|
|
|
- return new DxilInsertNoops();
|
|
|
|
|
|
+Pass *llvm::createDxilPreserveToSelectPass() {
|
|
|
|
+ return new DxilPreserveToSelect();
|
|
}
|
|
}
|
|
|
|
|
|
-INITIALIZE_PASS(DxilInsertNoops, "dxil-insert-noops", "Dxil Insert Noops", false, false)
|
|
|
|
|
|
+INITIALIZE_PASS(DxilPreserveToSelect, "dxil-insert-noops", "Dxil Insert Noops", false, false)
|
|
|
|
|
|
|
|
|
|
//==========================================================
|
|
//==========================================================
|
|
@@ -113,53 +451,82 @@ INITIALIZE_PASS(DxilInsertNoops, "dxil-insert-noops", "Dxil Insert Noops", false
|
|
|
|
|
|
namespace {
|
|
namespace {
|
|
|
|
|
|
-class DxilFinalizeNoops : public ModulePass {
|
|
|
|
|
|
+class DxilFinalizePreserves : public ModulePass {
|
|
public:
|
|
public:
|
|
static char ID;
|
|
static char ID;
|
|
GlobalVariable *NothingGV = nullptr;
|
|
GlobalVariable *NothingGV = nullptr;
|
|
|
|
|
|
- DxilFinalizeNoops() : ModulePass(ID) {
|
|
|
|
- initializeDxilFinalizeNoopsPass(*PassRegistry::getPassRegistry());
|
|
|
|
|
|
+ DxilFinalizePreserves() : ModulePass(ID) {
|
|
|
|
+ initializeDxilFinalizePreservesPass(*PassRegistry::getPassRegistry());
|
|
}
|
|
}
|
|
|
|
|
|
Instruction *GetFinalNoopInst(Module &M, Instruction *InsertBefore) {
|
|
Instruction *GetFinalNoopInst(Module &M, Instruction *InsertBefore) {
|
|
- if (!NothingGV) {
|
|
|
|
- NothingGV = M.getGlobalVariable(kNothingName);
|
|
|
|
if (!NothingGV) {
|
|
if (!NothingGV) {
|
|
- Type *i32Ty = Type::getInt32Ty(M.getContext());
|
|
|
|
- NothingGV = new GlobalVariable(M,
|
|
|
|
- i32Ty, true,
|
|
|
|
- llvm::GlobalValue::InternalLinkage,
|
|
|
|
- llvm::ConstantInt::get(i32Ty, 0), kNothingName);
|
|
|
|
|
|
+ NothingGV = M.getGlobalVariable(kNothingName);
|
|
|
|
+ if (!NothingGV) {
|
|
|
|
+ Type *i32Ty = Type::getInt32Ty(M.getContext());
|
|
|
|
+ NothingGV = new GlobalVariable(M,
|
|
|
|
+ i32Ty, true,
|
|
|
|
+ llvm::GlobalValue::InternalLinkage,
|
|
|
|
+ llvm::ConstantInt::get(i32Ty, 0), kNothingName);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- return new llvm::LoadInst(NothingGV, nullptr, InsertBefore);
|
|
|
|
-}
|
|
|
|
|
|
+ return new llvm::LoadInst(NothingGV, nullptr, InsertBefore);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ bool LowerPreserves(Module &M);
|
|
|
|
+ bool LowerNoops(Module &M);
|
|
bool runOnModule(Module &M) override;
|
|
bool runOnModule(Module &M) override;
|
|
- const char *getPassName() const override { return "Dxil Finalize Noops"; }
|
|
|
|
|
|
+ const char *getPassName() const override { return "Dxil Finalize Preserves"; }
|
|
};
|
|
};
|
|
|
|
|
|
-char DxilFinalizeNoops::ID;
|
|
|
|
|
|
+char DxilFinalizePreserves::ID;
|
|
}
|
|
}
|
|
|
|
|
|
-// Replace all @dx.noop's with @llvm.donothing
|
|
|
|
-bool DxilFinalizeNoops::runOnModule(Module &M) {
|
|
|
|
|
|
+// Fix undefs in the dx.preserve -> selects
|
|
|
|
+bool DxilFinalizePreserves::LowerPreserves(Module &M) {
|
|
|
|
+ bool Changed = false;
|
|
|
|
+
|
|
|
|
+ GlobalVariable *GV = M.getGlobalVariable(kPreserveName, true);
|
|
|
|
+ if (GV) {
|
|
|
|
+ for (User *U : GV->users()) {
|
|
|
|
+ LoadInst *LI = cast<LoadInst>(U);
|
|
|
|
+ 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;
|
|
Function *NoopF = nullptr;
|
|
for (Function &F : M) {
|
|
for (Function &F : M) {
|
|
if (!F.isDeclaration())
|
|
if (!F.isDeclaration())
|
|
continue;
|
|
continue;
|
|
- if (F.getName() == ::kNoopName) {
|
|
|
|
|
|
+ if (F.getName() == kNoopName) {
|
|
NoopF = &F;
|
|
NoopF = &F;
|
|
- break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (!NoopF)
|
|
|
|
- return false;
|
|
|
|
-
|
|
|
|
- if (!NoopF->user_empty()) {
|
|
|
|
|
|
+ if (NoopF) {
|
|
for (auto It = NoopF->user_begin(), E = NoopF->user_end(); It != E;) {
|
|
for (auto It = NoopF->user_begin(), E = NoopF->user_end(); It != E;) {
|
|
User *U = *(It++);
|
|
User *U = *(It++);
|
|
CallInst *CI = cast<CallInst>(U);
|
|
CallInst *CI = cast<CallInst>(U);
|
|
@@ -168,18 +535,29 @@ bool DxilFinalizeNoops::runOnModule(Module &M) {
|
|
Nop->setDebugLoc(CI->getDebugLoc());
|
|
Nop->setDebugLoc(CI->getDebugLoc());
|
|
|
|
|
|
CI->eraseFromParent();
|
|
CI->eraseFromParent();
|
|
|
|
+ Changed = true;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ assert(NoopF->user_empty() && "dx.noop calls must be all removed now");
|
|
|
|
+ NoopF->eraseFromParent();
|
|
}
|
|
}
|
|
|
|
|
|
- assert(NoopF->user_empty() && "dx.noop calls must be all removed now");
|
|
|
|
- NoopF->eraseFromParent();
|
|
|
|
|
|
+ return Changed;
|
|
|
|
+}
|
|
|
|
|
|
- return true;
|
|
|
|
|
|
+// Replace all preserves and nops
|
|
|
|
+bool DxilFinalizePreserves::runOnModule(Module &M) {
|
|
|
|
+ bool Changed = false;
|
|
|
|
+
|
|
|
|
+ Changed |= LowerPreserves(M);
|
|
|
|
+ Changed |= LowerNoops(M);
|
|
|
|
+
|
|
|
|
+ return Changed;
|
|
}
|
|
}
|
|
|
|
|
|
-Pass *llvm::createDxilFinalizeNoopsPass() {
|
|
|
|
- return new DxilFinalizeNoops();
|
|
|
|
|
|
+Pass *llvm::createDxilFinalizePreservesPass() {
|
|
|
|
+ return new DxilFinalizePreserves();
|
|
}
|
|
}
|
|
|
|
|
|
-INITIALIZE_PASS(DxilFinalizeNoops, "dxil-finalize-noops", "Dxil Finalize Noops", false, false)
|
|
|
|
|
|
+INITIALIZE_PASS(DxilFinalizePreserves, "dxil-finalize-preserves", "Dxil Finalize Preserves", false, false)
|
|
|
|
|