DirectXShaderCompiler/lib/HLSL/HLLegalizeParameter.cpp

331 строка
10 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// //
// HLLegalizeParameter.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. //
// //
// Legalize in parameter has write and out parameter has read. //
// Must be call before inline pass. //
///////////////////////////////////////////////////////////////////////////////
#include "dxc/DXIL/DxilOperations.h"
#include "dxc/DXIL/DxilTypeSystem.h"
#include "dxc/DXIL/DxilUtil.h"
#include "dxc/HLSL/DxilGenerationPass.h"
#include "dxc/HLSL/HLModule.h"
#include "dxc/HLSL/HLUtil.h"
#include "llvm/IR/IntrinsicInst.h"
#include "dxc/Support/Global.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Support/Casting.h"
#include <vector>
using namespace llvm;
using namespace hlsl;
// For parameter need to legalize, create alloca to replace all uses of it, and
// copy between the alloca and the parameter.
namespace {
class HLLegalizeParameter : public ModulePass {
public:
static char ID;
explicit HLLegalizeParameter() : ModulePass(ID) {}
bool runOnModule(Module &M) override;
private:
void patchWriteOnInParam(Function &F, Argument &Arg, const DataLayout &DL);
void patchReadOnOutParam(Function &F, Argument &Arg, const DataLayout &DL);
};
AllocaInst *createAllocaForPatch(Function &F, Type *Ty) {
IRBuilder<> Builder(F.getEntryBlock().getFirstInsertionPt());
return Builder.CreateAlloca(Ty);
}
void copyIn(AllocaInst *temp, Value *arg, CallInst *CI, unsigned size) {
if (size == 0)
return;
// Copy arg to temp before CI.
IRBuilder<> Builder(CI);
Builder.CreateMemCpy(temp, arg, size, 1);
}
void copyOut(AllocaInst *temp, Value *arg, CallInst *CI, unsigned size) {
if (size == 0)
return;
// Copy temp to arg after CI.
IRBuilder<> Builder(CI->getNextNode());
Builder.CreateMemCpy(arg, temp, size, 1);
}
bool isPointerNeedToLower(Value *V, Type *HandleTy) {
// CBuffer, Buffer, Texture....
// Anything related to dxil op.
// hl.subscript.
// Got to root of GEP.
while (GEPOperator *GEP = dyn_cast<GEPOperator>(V)) {
V = GEP->getPointerOperand();
}
CallInst *CI = dyn_cast<CallInst>(V);
if (!CI) {
// If array of vector, we need a copy to handle vector to array in
// LowerTypePasses.
Type *Ty = V->getType();
if (Ty->isPointerTy())
Ty = Ty->getPointerElementType();
if (!Ty->isArrayTy())
return false;
while (Ty->isArrayTy()) {
Ty = Ty->getArrayElementType();
}
return Ty->isVectorTy();
}
HLOpcodeGroup group = GetHLOpcodeGroup(CI->getCalledFunction());
if (group != HLOpcodeGroup::HLSubscript)
return false;
Value *Ptr = CI->getArgOperand(HLOperandIndex::kSubscriptObjectOpIdx);
// Ptr from resource handle.
if (Ptr->getType() == HandleTy)
return true;
unsigned Opcode = GetHLOpcode(CI);
// Ptr from cbuffer.
if (Opcode == (unsigned)HLSubscriptOpcode::CBufferSubscript)
return true;
return isPointerNeedToLower(Ptr, HandleTy);
}
bool mayAliasWithGlobal(Value *V, CallInst *CallSite,
std::vector<GlobalVariable *> &staticGVs) {
// The unsafe case need copy-in copy-out will be global variable alias with
// parameter. Then global variable is updated in the function, the parameter
// will be updated silently.
// Currently add copy for all non-const static global in
// CGMSHLSLRuntime::EmitHLSLOutParamConversionInit.
// So here just return false and do nothing.
// For case like
// struct T {
// float4 a[10];
//};
// static T g;
// void foo(inout T t) {
// // modify g
//}
// void bar() {
// T t = g;
// // Not copy because t is local.
// // But optimizations will change t to g later.
// foo(t);
//}
// Optimizations which remove the copy should not replace foo(t) into foo(g)
// when g could be modified.
// TODO: remove copy for global in
// CGMSHLSLRuntime::EmitHLSLOutParamConversionInit, do analysis to check alias
// only generate copy when there's alias.
return false;
}
struct CopyData {
CallInst *CallSite;
Value *Arg;
bool bCopyIn;
bool bCopyOut;
};
void ParameterCopyInCopyOut(hlsl::HLModule &HLM) {
Module &M = *HLM.GetModule();
Type *HandleTy = HLM.GetOP()->GetHandleType();
const DataLayout &DL = M.getDataLayout();
std::vector<GlobalVariable *> staticGVs;
for (GlobalVariable &GV : M.globals()) {
if (dxilutil::IsStaticGlobal(&GV) && !GV.isConstant()) {
staticGVs.emplace_back(&GV);
}
}
SmallVector<CopyData, 4> WorkList;
for (Function &F : M) {
if (F.user_empty())
continue;
DxilFunctionAnnotation *Annot = HLM.GetFunctionAnnotation(&F);
// Skip functions don't have annotation, include llvm intrinsic and HLOp
// functions.
if (!Annot)
continue;
bool bNoInline =
F.hasFnAttribute(llvm::Attribute::NoInline) || F.isDeclaration();
for (User *U : F.users()) {
CallInst *CI = dyn_cast<CallInst>(U);
if (!CI)
continue;
for (unsigned i = 0; i < CI->getNumArgOperands(); i++) {
Value *arg = CI->getArgOperand(i);
if (!arg->getType()->isPointerTy())
continue;
DxilParameterAnnotation &ParamAnnot = Annot->GetParameterAnnotation(i);
bool bCopyIn = false;
bool bCopyOut = false;
switch (ParamAnnot.GetParamInputQual()) {
default:
break;
case DxilParamInputQual::In: {
bCopyIn = true;
} break;
case DxilParamInputQual::Out: {
bCopyOut = true;
} break;
case DxilParamInputQual::Inout: {
bCopyIn = true;
bCopyOut = true;
} break;
}
if (!bCopyIn && !bCopyOut)
continue;
// When use ptr from cbuffer/buffer, need copy to avoid lower on user
// function.
bool bNeedCopy = mayAliasWithGlobal(arg, CI, staticGVs);
if (bNoInline)
bNeedCopy |= isPointerNeedToLower(arg, HandleTy);
if (!bNeedCopy)
continue;
CopyData data = {CI, arg, bCopyIn, bCopyOut};
WorkList.emplace_back(data);
}
}
}
for (CopyData &data : WorkList) {
CallInst *CI = data.CallSite;
Value *arg = data.Arg;
Type *Ty = arg->getType()->getPointerElementType();
Type *EltTy = dxilutil::GetArrayEltTy(Ty);
// Skip on object type and resource type.
if (dxilutil::IsHLSLObjectType(EltTy) ||
dxilutil::IsHLSLResourceType(EltTy))
continue;
unsigned size = DL.getTypeAllocSize(Ty);
AllocaInst *temp = createAllocaForPatch(*CI->getParent()->getParent(), Ty);
// TODO: Adding lifetime intrinsics isn't easy here, have to analyze uses.
if (data.bCopyIn)
copyIn(temp, arg, CI, size);
if (data.bCopyOut)
copyOut(temp, arg, CI, size);
CI->replaceUsesOfWith(arg, temp);
}
}
} // namespace
bool HLLegalizeParameter::runOnModule(Module &M) {
HLModule &HLM = M.GetOrCreateHLModule();
auto &typeSys = HLM.GetTypeSystem();
const DataLayout &DL = M.getDataLayout();
for (Function &F : M) {
if (F.isDeclaration())
continue;
DxilFunctionAnnotation *Annot = HLM.GetFunctionAnnotation(&F);
if (!Annot)
continue;
for (Argument &Arg : F.args()) {
if (!Arg.getType()->isPointerTy())
continue;
Type *EltTy = dxilutil::GetArrayEltTy(Arg.getType());
if (dxilutil::IsHLSLObjectType(EltTy) ||
dxilutil::IsHLSLResourceType(EltTy))
continue;
DxilParameterAnnotation &ParamAnnot =
Annot->GetParameterAnnotation(Arg.getArgNo());
switch (ParamAnnot.GetParamInputQual()) {
default:
break;
case DxilParamInputQual::In: {
hlutil::PointerStatus PS(&Arg, 0, /*bLdStOnly*/ true);
PS.analyze(typeSys, /*bStructElt*/ false);
if (PS.HasStored()) {
patchWriteOnInParam(F, Arg, DL);
}
} break;
case DxilParamInputQual::Out: {
hlutil::PointerStatus PS(&Arg, 0, /*bLdStOnly*/ true);
PS.analyze(typeSys, /*bStructElt*/ false);
if (PS.HasLoaded()) {
patchReadOnOutParam(F, Arg, DL);
}
}
}
}
}
// Copy-in copy-out for ptr arg when need.
ParameterCopyInCopyOut(HLM);
return true;
}
void HLLegalizeParameter::patchWriteOnInParam(Function &F, Argument &Arg,
const DataLayout &DL) {
// TODO: Adding lifetime intrinsics isn't easy here, have to analyze uses.
Type *Ty = Arg.getType()->getPointerElementType();
AllocaInst *temp = createAllocaForPatch(F, Ty);
Arg.replaceAllUsesWith(temp);
IRBuilder<> Builder(temp->getNextNode());
unsigned size = DL.getTypeAllocSize(Ty);
// copy arg to temp at beginning of function.
Builder.CreateMemCpy(temp, &Arg, size, 1);
}
void HLLegalizeParameter::patchReadOnOutParam(Function &F, Argument &Arg,
const DataLayout &DL) {
// TODO: Adding lifetime intrinsics isn't easy here, have to analyze uses.
Type *Ty = Arg.getType()->getPointerElementType();
AllocaInst *temp = createAllocaForPatch(F, Ty);
Arg.replaceAllUsesWith(temp);
unsigned size = DL.getTypeAllocSize(Ty);
for (auto &BB : F.getBasicBlockList()) {
// copy temp to arg before every return.
if (ReturnInst *RI = dyn_cast<ReturnInst>(BB.getTerminator())) {
IRBuilder<> RetBuilder(RI);
RetBuilder.CreateMemCpy(&Arg, temp, size, 1);
}
}
}
char HLLegalizeParameter::ID = 0;
ModulePass *llvm::createHLLegalizeParameter() {
return new HLLegalizeParameter();
}
INITIALIZE_PASS(HLLegalizeParameter, "hl-legalize-parameter",
"Legalize parameter", false, false)