llvm-project/llvm/lib/Target/DirectX/DXILForwardHandleAccesses.cpp
Farzon Lotfi 544562ebc2
[DirectX] Remove lifetime intrinsics and run Dead Store Elimination (#152636)
fixes #151764

This fix has two parts first we track all lifetime intrinsics and if
they are users of an alloca of a target extention like dx.RawBuffer then
we eliminate those memory intrinsics when we visit the alloca.

We do step one to allow us to use the Dead Store Elimination Pass. This
removes the alloca and simplifies the use of the target extention back
to using just the global. That keeps things in a form the
DXILBitcodeWriter is expecting.

Obviously to pull this off we needed to bring back the legacy pass
manager plumbing for the DSE pass and hook it up into the DirectX
backend.

The net impact of this change is that DML shader pass rate went from
89.72% (4268 successful compilations) to 90.98% (4328 successful
compilations).
2025-08-12 12:42:08 -04:00

220 lines
7.5 KiB
C++

//===- DXILForwardHandleAccesses.cpp - Cleanup Handles --------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "DXILForwardHandleAccesses.h"
#include "DXILShaderFlags.h"
#include "DirectX.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Analysis/DXILResource.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IntrinsicsDirectX.h"
#include "llvm/IR/Module.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Utils/Local.h"
#define DEBUG_TYPE "dxil-forward-handle-accesses"
using namespace llvm;
static void diagnoseAmbiguousHandle(IntrinsicInst *NewII,
IntrinsicInst *PrevII) {
Function *F = NewII->getFunction();
LLVMContext &Context = F->getParent()->getContext();
Context.diagnose(DiagnosticInfoGeneric(
Twine("Handle at \"") + NewII->getName() + "\" overwrites handle at \"" +
PrevII->getName() + "\""));
}
static void diagnoseHandleNotFound(LoadInst *LI) {
Function *F = LI->getFunction();
LLVMContext &Context = F->getParent()->getContext();
Context.diagnose(DiagnosticInfoGeneric(
LI, Twine("Load of \"") + LI->getPointerOperand()->getName() +
"\" is not a global resource handle"));
}
static void diagnoseUndominatedLoad(LoadInst *LI, IntrinsicInst *Handle) {
Function *F = LI->getFunction();
LLVMContext &Context = F->getParent()->getContext();
Context.diagnose(DiagnosticInfoGeneric(
LI, Twine("Load at \"") + LI->getName() +
"\" is not dominated by handle creation at \"" +
Handle->getName() + "\""));
}
static void
processHandle(IntrinsicInst *II,
DenseMap<GlobalVariable *, IntrinsicInst *> &HandleMap) {
for (User *U : II->users())
if (auto *SI = dyn_cast<StoreInst>(U))
if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand())) {
auto Entry = HandleMap.try_emplace(GV, II);
if (Entry.second)
LLVM_DEBUG(dbgs() << "Added " << GV->getName() << " to handle map\n");
else
diagnoseAmbiguousHandle(II, Entry.first->second);
}
}
static bool forwardHandleAccesses(Function &F, DominatorTree &DT) {
bool Changed = false;
DenseMap<GlobalVariable *, IntrinsicInst *> HandleMap;
SmallVector<LoadInst *> LoadsToProcess;
DenseMap<AllocaInst *, SmallVector<IntrinsicInst *>> LifeTimeIntrinsicMap;
for (BasicBlock &BB : F)
for (Instruction &Inst : BB)
if (auto *II = dyn_cast<IntrinsicInst>(&Inst)) {
switch (II->getIntrinsicID()) {
case Intrinsic::dx_resource_handlefrombinding:
case Intrinsic::dx_resource_handlefromimplicitbinding:
processHandle(II, HandleMap);
break;
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
if (II->arg_size() >= 1) {
Value *Ptr = II->getArgOperand(0);
if (auto *Alloca = dyn_cast<AllocaInst>(Ptr))
LifeTimeIntrinsicMap[Alloca].push_back(II);
}
break;
default:
continue;
}
} else if (auto *LI = dyn_cast<LoadInst>(&Inst))
if (isa<dxil::AnyResourceExtType>(LI->getType()))
LoadsToProcess.push_back(LI);
for (LoadInst *LI : LoadsToProcess) {
Value *V = LI->getPointerOperand();
auto *GV = dyn_cast<GlobalVariable>(V);
// If we didn't find the global, we may need to walk through a level of
// indirection. This generally happens at -O0.
if (!GV) {
if (auto *NestedLI = dyn_cast<LoadInst>(V)) {
BasicBlock::iterator BBI(NestedLI);
Value *Loaded = FindAvailableLoadedValue(
NestedLI, NestedLI->getParent(), BBI, 0, nullptr, nullptr);
GV = dyn_cast_or_null<GlobalVariable>(Loaded);
} else if (auto *NestedAlloca = dyn_cast<AllocaInst>(V)) {
if (auto It = LifeTimeIntrinsicMap.find(NestedAlloca);
It != LifeTimeIntrinsicMap.end()) {
llvm::for_each(It->second,
[](IntrinsicInst *II) { II->eraseFromParent(); });
LifeTimeIntrinsicMap.erase(It);
}
for (auto *User : NestedAlloca->users()) {
auto *Store = dyn_cast<StoreInst>(User);
if (!Store)
continue;
Value *StoredVal = Store->getValueOperand();
if (!StoredVal)
continue;
// Try direct global match
GV = dyn_cast<GlobalVariable>(StoredVal);
if (GV)
break;
// If it's a load, check its source
if (auto *Load = dyn_cast<LoadInst>(StoredVal)) {
GV = dyn_cast<GlobalVariable>(Load->getPointerOperand());
if (GV)
break;
// If loading from an unmodified stack copy of the global, reuse the
// global's value. Note: we are just repeating what we are doing for
// the load case for the alloca store pattern.
BasicBlock::iterator BBI(Load);
Value *Loaded = FindAvailableLoadedValue(Load, Load->getParent(),
BBI, 0, nullptr, nullptr);
GV = dyn_cast<GlobalVariable>(Loaded);
if (GV)
break;
}
}
}
}
auto It = HandleMap.find(GV);
if (It == HandleMap.end()) {
diagnoseHandleNotFound(LI);
continue;
}
Changed = true;
if (!DT.dominates(It->second, LI)) {
diagnoseUndominatedLoad(LI, It->second);
continue;
}
LLVM_DEBUG(dbgs() << "Replacing uses of " << GV->getName() << " at "
<< LI->getName() << " with " << It->second->getName()
<< "\n");
LI->replaceAllUsesWith(It->second);
LI->eraseFromParent();
}
return Changed;
}
PreservedAnalyses DXILForwardHandleAccesses::run(Function &F,
FunctionAnalysisManager &AM) {
PreservedAnalyses PA;
DominatorTree *DT = &AM.getResult<DominatorTreeAnalysis>(F);
bool Changed = forwardHandleAccesses(F, *DT);
if (!Changed)
return PreservedAnalyses::all();
return PA;
}
namespace {
class DXILForwardHandleAccessesLegacy : public FunctionPass {
public:
bool runOnFunction(Function &F) override {
DominatorTree *DT = &getAnalysis<DominatorTreeWrapperPass>().getDomTree();
return forwardHandleAccesses(F, *DT);
}
StringRef getPassName() const override {
return "DXIL Forward Handle Accesses";
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<DominatorTreeWrapperPass>();
}
DXILForwardHandleAccessesLegacy() : FunctionPass(ID) {}
static char ID; // Pass identification.
};
char DXILForwardHandleAccessesLegacy::ID = 0;
} // end anonymous namespace
INITIALIZE_PASS_BEGIN(DXILForwardHandleAccessesLegacy, DEBUG_TYPE,
"DXIL Forward Handle Accesses", false, false)
INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
INITIALIZE_PASS_END(DXILForwardHandleAccessesLegacy, DEBUG_TYPE,
"DXIL Forward Handle Accesses", false, false)
FunctionPass *llvm::createDXILForwardHandleAccessesLegacyPass() {
return new DXILForwardHandleAccessesLegacy();
}