//===--- SPIRVUtils.h ---- SPIR-V Utility Functions -------------*- 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 // //===----------------------------------------------------------------------===// // // This file contains miscellaneous utility functions. // //===----------------------------------------------------------------------===// #ifndef LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H #define LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H #include "MCTargetDesc/SPIRVBaseInfo.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/TypedPointerType.h" #include #include namespace llvm { class MCInst; class MachineFunction; class MachineInstr; class MachineInstrBuilder; class MachineIRBuilder; class MachineRegisterInfo; class Register; class StringRef; class SPIRVInstrInfo; class SPIRVSubtarget; // This class implements a partial ordering visitor, which visits a cyclic graph // in natural topological-like ordering. Topological ordering is not defined for // directed graphs with cycles, so this assumes cycles are a single node, and // ignores back-edges. The cycle is visited from the entry in the same // topological-like ordering. // // This means once we visit a node, we know all the possible ancestors have been // visited. // // clang-format off // // Given this graph: // // ,-> B -\ // A -+ +---> D ----> E -> F -> G -> H // `-> C -/ ^ | // +-----------------+ // // Visit order is: // A, [B, C in any order], D, E, F, G, H // // clang-format on // // Changing the function CFG between the construction of the visitor and // visiting is undefined. The visitor can be reused, but if the CFG is updated, // the visitor must be rebuilt. class PartialOrderingVisitor { DomTreeBuilder::BBDomTree DT; LoopInfo LI; std::unordered_set Visited = {}; struct OrderInfo { size_t Rank; size_t TraversalIndex; }; using BlockToOrderInfoMap = std::unordered_map; BlockToOrderInfoMap BlockToOrder; std::vector Order = {}; // Get all basic-blocks reachable from Start. std::unordered_set getReachableFrom(BasicBlock *Start); // Internal function used to determine the partial ordering. // Visits |BB| with the current rank being |Rank|. size_t visit(BasicBlock *BB, size_t Rank); public: // Build the visitor to operate on the function F. PartialOrderingVisitor(Function &F); // Returns true is |LHS| comes before |RHS| in the partial ordering. // If |LHS| and |RHS| have the same rank, the traversal order determines the // order (order is stable). bool compare(const BasicBlock *LHS, const BasicBlock *RHS) const; // Visit the function starting from the basic block |Start|, and calling |Op| // on each visited BB. This traversal ignores back-edges, meaning this won't // visit a node to which |Start| is not an ancestor. // If Op returns |true|, the visitor continues. If |Op| returns false, the // visitor will stop at that rank. This means if 2 nodes share the same rank, // and Op returns false when visiting the first, the second will be visited // afterwards. But none of their successors will. void partialOrderVisit(BasicBlock &Start, std::function Op); }; // Add the given string as a series of integer operand, inserting null // terminators and padding to make sure the operands all have 32-bit // little-endian words. void addStringImm(const StringRef &Str, MCInst &Inst); void addStringImm(const StringRef &Str, MachineInstrBuilder &MIB); void addStringImm(const StringRef &Str, IRBuilder<> &B, std::vector &Args); // Read the series of integer operands back as a null-terminated string using // the reverse of the logic in addStringImm. std::string getStringImm(const MachineInstr &MI, unsigned StartIndex); // Add the given numerical immediate to MIB. void addNumImm(const APInt &Imm, MachineInstrBuilder &MIB); // Add an OpName instruction for the given target register. void buildOpName(Register Target, const StringRef &Name, MachineIRBuilder &MIRBuilder); // Add an OpDecorate instruction for the given Reg. void buildOpDecorate(Register Reg, MachineIRBuilder &MIRBuilder, SPIRV::Decoration::Decoration Dec, const std::vector &DecArgs, StringRef StrImm = ""); void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII, SPIRV::Decoration::Decoration Dec, const std::vector &DecArgs, StringRef StrImm = ""); // Add an OpDecorate instruction by "spirv.Decorations" metadata node. void buildOpSpirvDecorations(Register Reg, MachineIRBuilder &MIRBuilder, const MDNode *GVarMD); // Convert a SPIR-V storage class to the corresponding LLVM IR address space. // TODO: maybe the following two functions should be handled in the subtarget // to allow for different OpenCL vs Vulkan handling. constexpr unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) { switch (SC) { case SPIRV::StorageClass::Function: return 0; case SPIRV::StorageClass::CrossWorkgroup: return 1; case SPIRV::StorageClass::UniformConstant: return 2; case SPIRV::StorageClass::Workgroup: return 3; case SPIRV::StorageClass::Generic: return 4; case SPIRV::StorageClass::DeviceOnlyINTEL: return 5; case SPIRV::StorageClass::HostOnlyINTEL: return 6; case SPIRV::StorageClass::Input: return 7; default: report_fatal_error("Unable to get address space id"); } } // Convert an LLVM IR address space to a SPIR-V storage class. SPIRV::StorageClass::StorageClass addressSpaceToStorageClass(unsigned AddrSpace, const SPIRVSubtarget &STI); SPIRV::MemorySemantics::MemorySemantics getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC); SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord); SPIRV::Scope::Scope getMemScope(LLVMContext &Ctx, SyncScope::ID Id); // Find def instruction for the given ConstReg, walking through // spv_track_constant and ASSIGN_TYPE instructions. Updates ConstReg by def // of OpConstant instruction. MachineInstr *getDefInstrMaybeConstant(Register &ConstReg, const MachineRegisterInfo *MRI); // Get constant integer value of the given ConstReg. uint64_t getIConstVal(Register ConstReg, const MachineRegisterInfo *MRI); // Check if MI is a SPIR-V specific intrinsic call. bool isSpvIntrinsic(const MachineInstr &MI, Intrinsic::ID IntrinsicID); // Get type of i-th operand of the metadata node. Type *getMDOperandAsType(const MDNode *N, unsigned I); // If OpenCL or SPIR-V builtin function name is recognized, return a demangled // name, otherwise return an empty string. std::string getOclOrSpirvBuiltinDemangledName(StringRef Name); // Check if a string contains a builtin prefix. bool hasBuiltinTypePrefix(StringRef Name); // Check if given LLVM type is a special opaque builtin type. bool isSpecialOpaqueType(const Type *Ty); // Check if the function is an SPIR-V entry point bool isEntryPoint(const Function &F); // Parse basic scalar type name, substring TypeName, and return LLVM type. Type *parseBasicTypeName(StringRef &TypeName, LLVMContext &Ctx); // Sort blocks in a partial ordering, so each block is after all its // dominators. This should match both the SPIR-V and the MIR requirements. // Returns true if the function was changed. bool sortBlocks(Function &F); // True if this is an instance of TypedPointerType. inline bool isTypedPointerTy(const Type *T) { return T && T->getTypeID() == Type::TypedPointerTyID; } // True if this is an instance of PointerType. inline bool isUntypedPointerTy(const Type *T) { return T && T->getTypeID() == Type::PointerTyID; } // True if this is an instance of PointerType or TypedPointerType. inline bool isPointerTy(const Type *T) { return isUntypedPointerTy(T) || isTypedPointerTy(T); } // Get the address space of this pointer or pointer vector type for instances of // PointerType or TypedPointerType. inline unsigned getPointerAddressSpace(const Type *T) { Type *SubT = T->getScalarType(); return SubT->getTypeID() == Type::PointerTyID ? cast(SubT)->getAddressSpace() : cast(SubT)->getAddressSpace(); } // Return true if the Argument is decorated with a pointee type inline bool hasPointeeTypeAttr(Argument *Arg) { return Arg->hasByValAttr() || Arg->hasByRefAttr() || Arg->hasStructRetAttr(); } // Return the pointee type of the argument or nullptr otherwise inline Type *getPointeeTypeByAttr(Argument *Arg) { if (Arg->hasByValAttr()) return Arg->getParamByValType(); if (Arg->hasStructRetAttr()) return Arg->getParamStructRetType(); if (Arg->hasByRefAttr()) return Arg->getParamByRefType(); return nullptr; } inline Type *reconstructFunctionType(Function *F) { SmallVector ArgTys; for (unsigned i = 0; i < F->arg_size(); ++i) ArgTys.push_back(F->getArg(i)->getType()); return FunctionType::get(F->getReturnType(), ArgTys, F->isVarArg()); } #define TYPED_PTR_TARGET_EXT_NAME "spirv.$TypedPointerType" inline Type *getTypedPointerWrapper(Type *ElemTy, unsigned AS) { return TargetExtType::get(ElemTy->getContext(), TYPED_PTR_TARGET_EXT_NAME, {ElemTy}, {AS}); } inline bool isTypedPointerWrapper(TargetExtType *ExtTy) { return ExtTy->getName() == TYPED_PTR_TARGET_EXT_NAME && ExtTy->getNumIntParameters() == 1 && ExtTy->getNumTypeParameters() == 1; } inline Type *applyWrappers(Type *Ty) { if (auto *ExtTy = dyn_cast(Ty)) { if (isTypedPointerWrapper(ExtTy)) return TypedPointerType::get(applyWrappers(ExtTy->getTypeParameter(0)), ExtTy->getIntParameter(0)); } else if (auto *VecTy = dyn_cast(Ty)) { Type *ElemTy = VecTy->getElementType(); Type *NewElemTy = ElemTy->isTargetExtTy() ? applyWrappers(ElemTy) : ElemTy; if (NewElemTy != ElemTy) return VectorType::get(NewElemTy, VecTy->getElementCount()); } return Ty; } inline Type *getPointeeType(Type *Ty) { if (auto PType = dyn_cast(Ty)) return PType->getElementType(); else if (auto *ExtTy = dyn_cast(Ty)) if (isTypedPointerWrapper(ExtTy)) return applyWrappers(ExtTy->getTypeParameter(0)); return nullptr; } inline bool isUntypedEquivalentToTyExt(Type *Ty1, Type *Ty2) { if (!isUntypedPointerTy(Ty1) || !Ty2) return false; if (auto *ExtTy = dyn_cast(Ty2)) if (isTypedPointerWrapper(ExtTy) && ExtTy->getTypeParameter(0) == IntegerType::getInt8Ty(Ty1->getContext()) && ExtTy->getIntParameter(0) == cast(Ty1)->getAddressSpace()) return true; return false; } inline bool isEquivalentTypes(Type *Ty1, Type *Ty2) { return isUntypedEquivalentToTyExt(Ty1, Ty2) || isUntypedEquivalentToTyExt(Ty2, Ty1); } inline Type *toTypedPointer(Type *Ty) { if (Type *NewTy = applyWrappers(Ty); NewTy != Ty) return NewTy; return isUntypedPointerTy(Ty) ? TypedPointerType::get(IntegerType::getInt8Ty(Ty->getContext()), getPointerAddressSpace(Ty)) : Ty; } inline Type *toTypedFunPointer(FunctionType *FTy) { Type *OrigRetTy = FTy->getReturnType(); Type *RetTy = toTypedPointer(OrigRetTy); bool IsUntypedPtr = false; for (Type *PTy : FTy->params()) { if (isUntypedPointerTy(PTy)) { IsUntypedPtr = true; break; } } if (!IsUntypedPtr && RetTy == OrigRetTy) return FTy; SmallVector ParamTys; for (Type *PTy : FTy->params()) ParamTys.push_back(toTypedPointer(PTy)); return FunctionType::get(RetTy, ParamTys, FTy->isVarArg()); } inline const Type *unifyPtrType(const Type *Ty) { if (auto FTy = dyn_cast(Ty)) return toTypedFunPointer(const_cast(FTy)); return toTypedPointer(const_cast(Ty)); } MachineInstr *getVRegDef(MachineRegisterInfo &MRI, Register Reg); } // namespace llvm #endif // LLVM_LIB_TARGET_SPIRV_SPIRVUTILS_H