diff options
Diffstat (limited to 'llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp')
| -rw-r--r-- | llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp | 363 |
1 files changed, 363 insertions, 0 deletions
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp new file mode 100644 index 000000000000..5cda6a07352d --- /dev/null +++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp @@ -0,0 +1,363 @@ +//===-- SPIRVLegalizePointerCast.cpp ----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// The LLVM IR has multiple legal patterns we cannot lower to Logical SPIR-V. +// This pass modifies such loads to have an IR we can directly lower to valid +// logical SPIR-V. +// OpenCL can avoid this because they rely on ptrcast, which is not supported +// by logical SPIR-V. +// +// This pass relies on the assign_ptr_type intrinsic to deduce the type of the +// pointed values, must replace all occurences of `ptrcast`. This is why +// unhandled cases are reported as unreachable: we MUST cover all cases. +// +// 1. Loading the first element of an array +// +// %array = [10 x i32] +// %value = load i32, ptr %array +// +// LLVM can skip the GEP instruction, and only request loading the first 4 +// bytes. In logical SPIR-V, we need an OpAccessChain to access the first +// element. This pass will add a getelementptr instruction before the load. +// +// +// 2. Implicit downcast from load +// +// %1 = getelementptr <4 x i32>, ptr %vec4, i64 0 +// %2 = load <3 x i32>, ptr %1 +// +// The pointer in the GEP instruction is only used for offset computations, +// but it doesn't NEED to match the pointed type. OpAccessChain however +// requires this. Also, LLVM loads define the bitwidth of the load, not the +// pointer. In this example, we can guess %vec4 is a vec4 thanks to the GEP +// instruction basetype, but we only want to load the first 3 elements, hence +// do a partial load. In logical SPIR-V, this is not legal. What we must do +// is load the full vector (basetype), extract 3 elements, and recombine them +// to form a 3-element vector. +// +//===----------------------------------------------------------------------===// + +#include "SPIRV.h" +#include "SPIRVSubtarget.h" +#include "SPIRVTargetMachine.h" +#include "SPIRVUtils.h" +#include "llvm/CodeGen/IntrinsicLowering.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/Intrinsics.h" +#include "llvm/IR/IntrinsicsSPIRV.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/LowerMemIntrinsics.h" + +using namespace llvm; + +namespace { +class SPIRVLegalizePointerCast : public FunctionPass { + + // Builds the `spv_assign_type` assigning |Ty| to |Value| at the current + // builder position. + void buildAssignType(IRBuilder<> &B, Type *Ty, Value *Arg) { + Value *OfType = PoisonValue::get(Ty); + CallInst *AssignCI = buildIntrWithMD(Intrinsic::spv_assign_type, + {Arg->getType()}, OfType, Arg, {}, B); + GR->addAssignPtrTypeInstr(Arg, AssignCI); + } + + // Loads parts of the vector of type |SourceType| from the pointer |Source| + // and create a new vector of type |TargetType|. |TargetType| must be a vector + // type, and element types of |TargetType| and |SourceType| must match. + // Returns the loaded value. + Value *loadVectorFromVector(IRBuilder<> &B, FixedVectorType *SourceType, + FixedVectorType *TargetType, Value *Source) { + // We expect the codegen to avoid doing implicit bitcast from a load. + assert(TargetType->getElementType() == SourceType->getElementType()); + assert(TargetType->getNumElements() < SourceType->getNumElements()); + + LoadInst *NewLoad = B.CreateLoad(SourceType, Source); + buildAssignType(B, SourceType, NewLoad); + + SmallVector<int> Mask(/* Size= */ TargetType->getNumElements()); + for (unsigned I = 0; I < TargetType->getNumElements(); ++I) + Mask[I] = I; + Value *Output = B.CreateShuffleVector(NewLoad, NewLoad, Mask); + buildAssignType(B, TargetType, Output); + return Output; + } + + // Loads the first value in an aggregate pointed by |Source| of containing + // elements of type |ElementType|. Load flags will be copied from |BadLoad|, + // which should be the load being legalized. Returns the loaded value. + Value *loadFirstValueFromAggregate(IRBuilder<> &B, Type *ElementType, + Value *Source, LoadInst *BadLoad) { + SmallVector<Type *, 2> Types = {BadLoad->getPointerOperandType(), + BadLoad->getPointerOperandType()}; + SmallVector<Value *, 3> Args{/* isInBounds= */ B.getInt1(false), Source, + B.getInt32(0), B.getInt32(0)}; + auto *GEP = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args}); + GR->buildAssignPtr(B, ElementType, GEP); + + LoadInst *LI = B.CreateLoad(ElementType, GEP); + LI->setAlignment(BadLoad->getAlign()); + buildAssignType(B, ElementType, LI); + return LI; + } + + // Replaces the load instruction to get rid of the ptrcast used as source + // operand. + void transformLoad(IRBuilder<> &B, LoadInst *LI, Value *CastedOperand, + Value *OriginalOperand) { + Type *FromTy = GR->findDeducedElementType(OriginalOperand); + Type *ToTy = GR->findDeducedElementType(CastedOperand); + Value *Output = nullptr; + + auto *SAT = dyn_cast<ArrayType>(FromTy); + auto *SVT = dyn_cast<FixedVectorType>(FromTy); + auto *SST = dyn_cast<StructType>(FromTy); + auto *DVT = dyn_cast<FixedVectorType>(ToTy); + + B.SetInsertPoint(LI); + + // Destination is the element type of Source, and source is an array -> + // Loading 1st element. + // - float a = array[0]; + if (SAT && SAT->getElementType() == ToTy) + Output = loadFirstValueFromAggregate(B, SAT->getElementType(), + OriginalOperand, LI); + // Destination is the element type of Source, and source is a vector -> + // Vector to scalar. + // - float a = vector.x; + else if (!DVT && SVT && SVT->getElementType() == ToTy) { + Output = loadFirstValueFromAggregate(B, SVT->getElementType(), + OriginalOperand, LI); + } + // Destination is a smaller vector than source. + // - float3 v3 = vector4; + else if (SVT && DVT) + Output = loadVectorFromVector(B, SVT, DVT, OriginalOperand); + // Destination is the scalar type stored at the start of an aggregate. + // - struct S { float m }; + // - float v = s.m; + else if (SST && SST->getTypeAtIndex(0u) == ToTy) + Output = loadFirstValueFromAggregate(B, ToTy, OriginalOperand, LI); + else + llvm_unreachable("Unimplemented implicit down-cast from load."); + + GR->replaceAllUsesWith(LI, Output, /* DeleteOld= */ true); + DeadInstructions.push_back(LI); + } + + // Creates an spv_insertelt instruction (equivalent to llvm's insertelement). + Value *makeInsertElement(IRBuilder<> &B, Value *Vector, Value *Element, + unsigned Index) { + Type *Int32Ty = Type::getInt32Ty(B.getContext()); + SmallVector<Type *, 4> Types = {Vector->getType(), Vector->getType(), + Element->getType(), Int32Ty}; + SmallVector<Value *> Args = {Vector, Element, B.getInt32(Index)}; + Instruction *NewI = + B.CreateIntrinsic(Intrinsic::spv_insertelt, {Types}, {Args}); + buildAssignType(B, Vector->getType(), NewI); + return NewI; + } + + // Creates an spv_extractelt instruction (equivalent to llvm's + // extractelement). + Value *makeExtractElement(IRBuilder<> &B, Type *ElementType, Value *Vector, + unsigned Index) { + Type *Int32Ty = Type::getInt32Ty(B.getContext()); + SmallVector<Type *, 3> Types = {ElementType, Vector->getType(), Int32Ty}; + SmallVector<Value *> Args = {Vector, B.getInt32(Index)}; + Instruction *NewI = + B.CreateIntrinsic(Intrinsic::spv_extractelt, {Types}, {Args}); + buildAssignType(B, ElementType, NewI); + return NewI; + } + + // Stores the given Src vector operand into the Dst vector, adjusting the size + // if required. + Value *storeVectorFromVector(IRBuilder<> &B, Value *Src, Value *Dst, + Align Alignment) { + FixedVectorType *SrcType = cast<FixedVectorType>(Src->getType()); + FixedVectorType *DstType = + cast<FixedVectorType>(GR->findDeducedElementType(Dst)); + assert(DstType->getNumElements() >= SrcType->getNumElements()); + + LoadInst *LI = B.CreateLoad(DstType, Dst); + LI->setAlignment(Alignment); + Value *OldValues = LI; + buildAssignType(B, OldValues->getType(), OldValues); + Value *NewValues = Src; + + for (unsigned I = 0; I < SrcType->getNumElements(); ++I) { + Value *Element = + makeExtractElement(B, SrcType->getElementType(), NewValues, I); + OldValues = makeInsertElement(B, OldValues, Element, I); + } + + StoreInst *SI = B.CreateStore(OldValues, Dst); + SI->setAlignment(Alignment); + return SI; + } + + void buildGEPIndexChain(IRBuilder<> &B, Type *Search, Type *Aggregate, + SmallVectorImpl<Value *> &Indices) { + Indices.push_back(B.getInt32(0)); + + if (Search == Aggregate) + return; + + if (auto *ST = dyn_cast<StructType>(Aggregate)) + buildGEPIndexChain(B, Search, ST->getTypeAtIndex(0u), Indices); + else if (auto *AT = dyn_cast<ArrayType>(Aggregate)) + buildGEPIndexChain(B, Search, AT->getElementType(), Indices); + else if (auto *VT = dyn_cast<FixedVectorType>(Aggregate)) + buildGEPIndexChain(B, Search, VT->getElementType(), Indices); + else + llvm_unreachable("Bad access chain?"); + } + + // Stores the given Src value into the first entry of the Dst aggregate. + Value *storeToFirstValueAggregate(IRBuilder<> &B, Value *Src, Value *Dst, + Type *DstPointeeType, Align Alignment) { + SmallVector<Type *, 2> Types = {Dst->getType(), Dst->getType()}; + SmallVector<Value *, 3> Args{/* isInBounds= */ B.getInt1(true), Dst}; + buildGEPIndexChain(B, Src->getType(), DstPointeeType, Args); + auto *GEP = B.CreateIntrinsic(Intrinsic::spv_gep, {Types}, {Args}); + GR->buildAssignPtr(B, Src->getType(), GEP); + StoreInst *SI = B.CreateStore(Src, GEP); + SI->setAlignment(Alignment); + return SI; + } + + bool isTypeFirstElementAggregate(Type *Search, Type *Aggregate) { + if (Search == Aggregate) + return true; + if (auto *ST = dyn_cast<StructType>(Aggregate)) + return isTypeFirstElementAggregate(Search, ST->getTypeAtIndex(0u)); + if (auto *VT = dyn_cast<FixedVectorType>(Aggregate)) + return isTypeFirstElementAggregate(Search, VT->getElementType()); + if (auto *AT = dyn_cast<ArrayType>(Aggregate)) + return isTypeFirstElementAggregate(Search, AT->getElementType()); + return false; + } + + // Transforms a store instruction (or SPV intrinsic) using a ptrcast as + // operand into a valid logical SPIR-V store with no ptrcast. + void transformStore(IRBuilder<> &B, Instruction *BadStore, Value *Src, + Value *Dst, Align Alignment) { + Type *ToTy = GR->findDeducedElementType(Dst); + Type *FromTy = Src->getType(); + + auto *S_VT = dyn_cast<FixedVectorType>(FromTy); + auto *D_ST = dyn_cast<StructType>(ToTy); + auto *D_VT = dyn_cast<FixedVectorType>(ToTy); + + B.SetInsertPoint(BadStore); + if (D_ST && isTypeFirstElementAggregate(FromTy, D_ST)) + storeToFirstValueAggregate(B, Src, Dst, D_ST, Alignment); + else if (D_VT && S_VT) + storeVectorFromVector(B, Src, Dst, Alignment); + else if (D_VT && !S_VT && FromTy == D_VT->getElementType()) + storeToFirstValueAggregate(B, Src, Dst, D_VT, Alignment); + else + llvm_unreachable("Unsupported ptrcast use in store. Please fix."); + + DeadInstructions.push_back(BadStore); + } + + void legalizePointerCast(IntrinsicInst *II) { + Value *CastedOperand = II; + Value *OriginalOperand = II->getOperand(0); + + IRBuilder<> B(II->getContext()); + std::vector<Value *> Users; + for (Use &U : II->uses()) + Users.push_back(U.getUser()); + + for (Value *User : Users) { + if (LoadInst *LI = dyn_cast<LoadInst>(User)) { + transformLoad(B, LI, CastedOperand, OriginalOperand); + continue; + } + + if (StoreInst *SI = dyn_cast<StoreInst>(User)) { + transformStore(B, SI, SI->getValueOperand(), OriginalOperand, + SI->getAlign()); + continue; + } + + if (IntrinsicInst *Intrin = dyn_cast<IntrinsicInst>(User)) { + if (Intrin->getIntrinsicID() == Intrinsic::spv_assign_ptr_type) { + DeadInstructions.push_back(Intrin); + continue; + } + + if (Intrin->getIntrinsicID() == Intrinsic::spv_gep) { + GR->replaceAllUsesWith(CastedOperand, OriginalOperand, + /* DeleteOld= */ false); + continue; + } + + if (Intrin->getIntrinsicID() == Intrinsic::spv_store) { + Align Alignment; + if (ConstantInt *C = dyn_cast<ConstantInt>(Intrin->getOperand(3))) + Alignment = Align(C->getZExtValue()); + transformStore(B, Intrin, Intrin->getArgOperand(0), OriginalOperand, + Alignment); + continue; + } + } + + llvm_unreachable("Unsupported ptrcast user. Please fix."); + } + + DeadInstructions.push_back(II); + } + +public: + SPIRVLegalizePointerCast(SPIRVTargetMachine *TM) : FunctionPass(ID), TM(TM) {} + + virtual bool runOnFunction(Function &F) override { + const SPIRVSubtarget &ST = TM->getSubtarget<SPIRVSubtarget>(F); + GR = ST.getSPIRVGlobalRegistry(); + DeadInstructions.clear(); + + std::vector<IntrinsicInst *> WorkList; + for (auto &BB : F) { + for (auto &I : BB) { + auto *II = dyn_cast<IntrinsicInst>(&I); + if (II && II->getIntrinsicID() == Intrinsic::spv_ptrcast) + WorkList.push_back(II); + } + } + + for (IntrinsicInst *II : WorkList) + legalizePointerCast(II); + + for (Instruction *I : DeadInstructions) + I->eraseFromParent(); + + return DeadInstructions.size() != 0; + } + +private: + SPIRVTargetMachine *TM = nullptr; + SPIRVGlobalRegistry *GR = nullptr; + std::vector<Instruction *> DeadInstructions; + +public: + static char ID; +}; +} // namespace + +char SPIRVLegalizePointerCast::ID = 0; +INITIALIZE_PASS(SPIRVLegalizePointerCast, "spirv-legalize-bitcast", + "SPIRV legalize bitcast pass", false, false) + +FunctionPass *llvm::createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM) { + return new SPIRVLegalizePointerCast(TM); +} |
