//===-- SPIRVPreLegalizer.cpp - prepare IR for legalization -----*- 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 pass prepares IR for legalization: it assigns SPIR-V types to registers // and removes intrinsics which holded these types during IR translation. // Also it processes constants and registers them in GR to avoid duplication. // //===----------------------------------------------------------------------===// #include "SPIRV.h" #include "SPIRVSubtarget.h" #include "SPIRVUtils.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/Analysis/OptimizationRemarkEmitter.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugInfoMetadata.h" #include "llvm/IR/IntrinsicsSPIRV.h" #include "llvm/Target/TargetIntrinsicInfo.h" #define DEBUG_TYPE "spirv-prelegalizer" using namespace llvm; namespace { class SPIRVPreLegalizer : public MachineFunctionPass { public: static char ID; SPIRVPreLegalizer() : MachineFunctionPass(ID) { initializeSPIRVPreLegalizerPass(*PassRegistry::getPassRegistry()); } bool runOnMachineFunction(MachineFunction &MF) override; }; } // namespace static void addConstantsToTrack(MachineFunction &MF, SPIRVGlobalRegistry *GR) { MachineRegisterInfo &MRI = MF.getRegInfo(); DenseMap RegsAlreadyAddedToDT; SmallVector ToErase, ToEraseComposites; for (MachineBasicBlock &MBB : MF) { for (MachineInstr &MI : MBB) { if (!isSpvIntrinsic(MI, Intrinsic::spv_track_constant)) continue; ToErase.push_back(&MI); auto *Const = cast(cast( MI.getOperand(3).getMetadata()->getOperand(0)) ->getValue()); if (auto *GV = dyn_cast(Const)) { Register Reg = GR->find(GV, &MF); if (!Reg.isValid()) GR->add(GV, &MF, MI.getOperand(2).getReg()); else RegsAlreadyAddedToDT[&MI] = Reg; } else { Register Reg = GR->find(Const, &MF); if (!Reg.isValid()) { if (auto *ConstVec = dyn_cast(Const)) { auto *BuildVec = MRI.getVRegDef(MI.getOperand(2).getReg()); assert(BuildVec && BuildVec->getOpcode() == TargetOpcode::G_BUILD_VECTOR); for (unsigned i = 0; i < ConstVec->getNumElements(); ++i) GR->add(ConstVec->getElementAsConstant(i), &MF, BuildVec->getOperand(1 + i).getReg()); } GR->add(Const, &MF, MI.getOperand(2).getReg()); } else { RegsAlreadyAddedToDT[&MI] = Reg; // This MI is unused and will be removed. If the MI uses // const_composite, it will be unused and should be removed too. assert(MI.getOperand(2).isReg() && "Reg operand is expected"); MachineInstr *SrcMI = MRI.getVRegDef(MI.getOperand(2).getReg()); if (SrcMI && isSpvIntrinsic(*SrcMI, Intrinsic::spv_const_composite)) ToEraseComposites.push_back(SrcMI); } } } } for (MachineInstr *MI : ToErase) { Register Reg = MI->getOperand(2).getReg(); if (RegsAlreadyAddedToDT.contains(MI)) Reg = RegsAlreadyAddedToDT[MI]; auto *RC = MRI.getRegClassOrNull(MI->getOperand(0).getReg()); if (!MRI.getRegClassOrNull(Reg) && RC) MRI.setRegClass(Reg, RC); MRI.replaceRegWith(MI->getOperand(0).getReg(), Reg); MI->eraseFromParent(); } for (MachineInstr *MI : ToEraseComposites) MI->eraseFromParent(); } static void foldConstantsIntoIntrinsics(MachineFunction &MF) { SmallVector ToErase; MachineRegisterInfo &MRI = MF.getRegInfo(); const unsigned AssignNameOperandShift = 2; for (MachineBasicBlock &MBB : MF) { for (MachineInstr &MI : MBB) { if (!isSpvIntrinsic(MI, Intrinsic::spv_assign_name)) continue; unsigned NumOp = MI.getNumExplicitDefs() + AssignNameOperandShift; while (MI.getOperand(NumOp).isReg()) { MachineOperand &MOp = MI.getOperand(NumOp); MachineInstr *ConstMI = MRI.getVRegDef(MOp.getReg()); assert(ConstMI->getOpcode() == TargetOpcode::G_CONSTANT); MI.removeOperand(NumOp); MI.addOperand(MachineOperand::CreateImm( ConstMI->getOperand(1).getCImm()->getZExtValue())); if (MRI.use_empty(ConstMI->getOperand(0).getReg())) ToErase.push_back(ConstMI); } } } for (MachineInstr *MI : ToErase) MI->eraseFromParent(); } static void insertBitcasts(MachineFunction &MF, SPIRVGlobalRegistry *GR, MachineIRBuilder MIB) { // Get access to information about available extensions const SPIRVSubtarget *ST = static_cast(&MIB.getMF().getSubtarget()); SmallVector ToErase; for (MachineBasicBlock &MBB : MF) { for (MachineInstr &MI : MBB) { if (!isSpvIntrinsic(MI, Intrinsic::spv_bitcast) && !isSpvIntrinsic(MI, Intrinsic::spv_ptrcast)) continue; assert(MI.getOperand(2).isReg()); MIB.setInsertPt(*MI.getParent(), MI); ToErase.push_back(&MI); if (isSpvIntrinsic(MI, Intrinsic::spv_bitcast)) { MIB.buildBitcast(MI.getOperand(0).getReg(), MI.getOperand(2).getReg()); continue; } Register Def = MI.getOperand(0).getReg(); Register Source = MI.getOperand(2).getReg(); SPIRVType *BaseTy = GR->getOrCreateSPIRVType( getMDOperandAsType(MI.getOperand(3).getMetadata(), 0), MIB); SPIRVType *AssignedPtrType = GR->getOrCreateSPIRVPointerType( BaseTy, MI, *MF.getSubtarget().getInstrInfo(), addressSpaceToStorageClass(MI.getOperand(4).getImm(), *ST)); // If the bitcast would be redundant, replace all uses with the source // register. if (GR->getSPIRVTypeForVReg(Source) == AssignedPtrType) { MIB.getMRI()->replaceRegWith(Def, Source); } else { GR->assignSPIRVTypeToVReg(AssignedPtrType, Def, MF); MIB.buildBitcast(Def, Source); } } } for (MachineInstr *MI : ToErase) MI->eraseFromParent(); } // Translating GV, IRTranslator sometimes generates following IR: // %1 = G_GLOBAL_VALUE // %2 = COPY %1 // %3 = G_ADDRSPACE_CAST %2 // New registers have no SPIRVType and no register class info. // // Set SPIRVType for GV, propagate it from GV to other instructions, // also set register classes. static SPIRVType *propagateSPIRVType(MachineInstr *MI, SPIRVGlobalRegistry *GR, MachineRegisterInfo &MRI, MachineIRBuilder &MIB) { SPIRVType *SpirvTy = nullptr; assert(MI && "Machine instr is expected"); if (MI->getOperand(0).isReg()) { Register Reg = MI->getOperand(0).getReg(); SpirvTy = GR->getSPIRVTypeForVReg(Reg); if (!SpirvTy) { switch (MI->getOpcode()) { case TargetOpcode::G_CONSTANT: { MIB.setInsertPt(*MI->getParent(), MI); Type *Ty = MI->getOperand(1).getCImm()->getType(); SpirvTy = GR->getOrCreateSPIRVType(Ty, MIB); break; } case TargetOpcode::G_GLOBAL_VALUE: { MIB.setInsertPt(*MI->getParent(), MI); const GlobalValue *Global = MI->getOperand(1).getGlobal(); Type *ElementTy = GR->getDeducedGlobalValueType(Global); auto *Ty = TypedPointerType::get(ElementTy, Global->getType()->getAddressSpace()); SpirvTy = GR->getOrCreateSPIRVType(Ty, MIB); break; } case TargetOpcode::G_TRUNC: case TargetOpcode::G_ADDRSPACE_CAST: case TargetOpcode::G_PTR_ADD: case TargetOpcode::COPY: { MachineOperand &Op = MI->getOperand(1); MachineInstr *Def = Op.isReg() ? MRI.getVRegDef(Op.getReg()) : nullptr; if (Def) SpirvTy = propagateSPIRVType(Def, GR, MRI, MIB); break; } default: break; } if (SpirvTy) GR->assignSPIRVTypeToVReg(SpirvTy, Reg, MIB.getMF()); if (!MRI.getRegClassOrNull(Reg)) MRI.setRegClass(Reg, &SPIRV::IDRegClass); } } return SpirvTy; } static std::pair createNewIdReg(Register ValReg, unsigned Opcode, MachineRegisterInfo &MRI, const SPIRVGlobalRegistry &GR) { LLT NewT = LLT::scalar(32); SPIRVType *SpvType = GR.getSPIRVTypeForVReg(ValReg); assert(SpvType && "VReg is expected to have SPIRV type"); bool IsFloat = SpvType->getOpcode() == SPIRV::OpTypeFloat; bool IsVectorFloat = SpvType->getOpcode() == SPIRV::OpTypeVector && GR.getSPIRVTypeForVReg(SpvType->getOperand(1).getReg())->getOpcode() == SPIRV::OpTypeFloat; IsFloat |= IsVectorFloat; auto GetIdOp = IsFloat ? SPIRV::GET_fID : SPIRV::GET_ID; auto DstClass = IsFloat ? &SPIRV::fIDRegClass : &SPIRV::IDRegClass; if (MRI.getType(ValReg).isPointer()) { NewT = LLT::pointer(0, 32); GetIdOp = SPIRV::GET_pID; DstClass = &SPIRV::pIDRegClass; } else if (MRI.getType(ValReg).isVector()) { NewT = LLT::fixed_vector(2, NewT); GetIdOp = IsFloat ? SPIRV::GET_vfID : SPIRV::GET_vID; DstClass = IsFloat ? &SPIRV::vfIDRegClass : &SPIRV::vIDRegClass; } Register IdReg = MRI.createGenericVirtualRegister(NewT); MRI.setRegClass(IdReg, DstClass); return {IdReg, GetIdOp}; } // Insert ASSIGN_TYPE instuction between Reg and its definition, set NewReg as // a dst of the definition, assign SPIRVType to both registers. If SpirvTy is // provided, use it as SPIRVType in ASSIGN_TYPE, otherwise create it from Ty. // It's used also in SPIRVBuiltins.cpp. // TODO: maybe move to SPIRVUtils. namespace llvm { Register insertAssignInstr(Register Reg, Type *Ty, SPIRVType *SpirvTy, SPIRVGlobalRegistry *GR, MachineIRBuilder &MIB, MachineRegisterInfo &MRI) { MachineInstr *Def = MRI.getVRegDef(Reg); assert((Ty || SpirvTy) && "Either LLVM or SPIRV type is expected."); MIB.setInsertPt(*Def->getParent(), (Def->getNextNode() ? Def->getNextNode()->getIterator() : Def->getParent()->end())); Register NewReg = MRI.createGenericVirtualRegister(MRI.getType(Reg)); if (auto *RC = MRI.getRegClassOrNull(Reg)) { MRI.setRegClass(NewReg, RC); } else { MRI.setRegClass(NewReg, &SPIRV::IDRegClass); MRI.setRegClass(Reg, &SPIRV::IDRegClass); } SpirvTy = SpirvTy ? SpirvTy : GR->getOrCreateSPIRVType(Ty, MIB); GR->assignSPIRVTypeToVReg(SpirvTy, Reg, MIB.getMF()); // This is to make it convenient for Legalizer to get the SPIRVType // when processing the actual MI (i.e. not pseudo one). GR->assignSPIRVTypeToVReg(SpirvTy, NewReg, MIB.getMF()); // Copy MIFlags from Def to ASSIGN_TYPE instruction. It's required to keep // the flags after instruction selection. const uint32_t Flags = Def->getFlags(); MIB.buildInstr(SPIRV::ASSIGN_TYPE) .addDef(Reg) .addUse(NewReg) .addUse(GR->getSPIRVTypeID(SpirvTy)) .setMIFlags(Flags); Def->getOperand(0).setReg(NewReg); return NewReg; } void processInstr(MachineInstr &MI, MachineIRBuilder &MIB, MachineRegisterInfo &MRI, SPIRVGlobalRegistry *GR) { unsigned Opc = MI.getOpcode(); assert(MI.getNumDefs() > 0 && MRI.hasOneUse(MI.getOperand(0).getReg())); MachineInstr &AssignTypeInst = *(MRI.use_instr_begin(MI.getOperand(0).getReg())); auto NewReg = createNewIdReg(MI.getOperand(0).getReg(), Opc, MRI, *GR).first; AssignTypeInst.getOperand(1).setReg(NewReg); MI.getOperand(0).setReg(NewReg); MIB.setInsertPt(*MI.getParent(), (MI.getNextNode() ? MI.getNextNode()->getIterator() : MI.getParent()->end())); for (auto &Op : MI.operands()) { if (!Op.isReg() || Op.isDef()) continue; auto IdOpInfo = createNewIdReg(Op.getReg(), Opc, MRI, *GR); MIB.buildInstr(IdOpInfo.second).addDef(IdOpInfo.first).addUse(Op.getReg()); Op.setReg(IdOpInfo.first); } } } // namespace llvm static void generateAssignInstrs(MachineFunction &MF, SPIRVGlobalRegistry *GR, MachineIRBuilder MIB) { // Get access to information about available extensions const SPIRVSubtarget *ST = static_cast(&MIB.getMF().getSubtarget()); MachineRegisterInfo &MRI = MF.getRegInfo(); SmallVector ToErase; for (MachineBasicBlock *MBB : post_order(&MF)) { if (MBB->empty()) continue; bool ReachedBegin = false; for (auto MII = std::prev(MBB->end()), Begin = MBB->begin(); !ReachedBegin;) { MachineInstr &MI = *MII; if (isSpvIntrinsic(MI, Intrinsic::spv_assign_ptr_type)) { Register Reg = MI.getOperand(1).getReg(); MIB.setInsertPt(*MI.getParent(), MI.getIterator()); SPIRVType *BaseTy = GR->getOrCreateSPIRVType( getMDOperandAsType(MI.getOperand(2).getMetadata(), 0), MIB); SPIRVType *AssignedPtrType = GR->getOrCreateSPIRVPointerType( BaseTy, MI, *MF.getSubtarget().getInstrInfo(), addressSpaceToStorageClass(MI.getOperand(3).getImm(), *ST)); MachineInstr *Def = MRI.getVRegDef(Reg); assert(Def && "Expecting an instruction that defines the register"); // G_GLOBAL_VALUE already has type info. if (Def->getOpcode() != TargetOpcode::G_GLOBAL_VALUE) insertAssignInstr(Reg, nullptr, AssignedPtrType, GR, MIB, MF.getRegInfo()); ToErase.push_back(&MI); } else if (isSpvIntrinsic(MI, Intrinsic::spv_assign_type)) { Register Reg = MI.getOperand(1).getReg(); Type *Ty = getMDOperandAsType(MI.getOperand(2).getMetadata(), 0); MachineInstr *Def = MRI.getVRegDef(Reg); assert(Def && "Expecting an instruction that defines the register"); // G_GLOBAL_VALUE already has type info. if (Def->getOpcode() != TargetOpcode::G_GLOBAL_VALUE) insertAssignInstr(Reg, Ty, nullptr, GR, MIB, MF.getRegInfo()); ToErase.push_back(&MI); } else if (MI.getOpcode() == TargetOpcode::G_CONSTANT || MI.getOpcode() == TargetOpcode::G_FCONSTANT || MI.getOpcode() == TargetOpcode::G_BUILD_VECTOR) { // %rc = G_CONSTANT ty Val // ===> // %cty = OpType* ty // %rctmp = G_CONSTANT ty Val // %rc = ASSIGN_TYPE %rctmp, %cty Register Reg = MI.getOperand(0).getReg(); if (MRI.hasOneUse(Reg)) { MachineInstr &UseMI = *MRI.use_instr_begin(Reg); if (isSpvIntrinsic(UseMI, Intrinsic::spv_assign_type) || isSpvIntrinsic(UseMI, Intrinsic::spv_assign_name)) continue; } Type *Ty = nullptr; if (MI.getOpcode() == TargetOpcode::G_CONSTANT) Ty = MI.getOperand(1).getCImm()->getType(); else if (MI.getOpcode() == TargetOpcode::G_FCONSTANT) Ty = MI.getOperand(1).getFPImm()->getType(); else { assert(MI.getOpcode() == TargetOpcode::G_BUILD_VECTOR); Type *ElemTy = nullptr; MachineInstr *ElemMI = MRI.getVRegDef(MI.getOperand(1).getReg()); assert(ElemMI); if (ElemMI->getOpcode() == TargetOpcode::G_CONSTANT) ElemTy = ElemMI->getOperand(1).getCImm()->getType(); else if (ElemMI->getOpcode() == TargetOpcode::G_FCONSTANT) ElemTy = ElemMI->getOperand(1).getFPImm()->getType(); else llvm_unreachable("Unexpected opcode"); unsigned NumElts = MI.getNumExplicitOperands() - MI.getNumExplicitDefs(); Ty = VectorType::get(ElemTy, NumElts, false); } insertAssignInstr(Reg, Ty, nullptr, GR, MIB, MRI); } else if (MI.getOpcode() == TargetOpcode::G_TRUNC || MI.getOpcode() == TargetOpcode::G_GLOBAL_VALUE || MI.getOpcode() == TargetOpcode::COPY || MI.getOpcode() == TargetOpcode::G_ADDRSPACE_CAST) { propagateSPIRVType(&MI, GR, MRI, MIB); } if (MII == Begin) ReachedBegin = true; else --MII; } } for (MachineInstr *MI : ToErase) MI->eraseFromParent(); } // Defined in SPIRVLegalizerInfo.cpp. extern bool isTypeFoldingSupported(unsigned Opcode); static void processInstrsWithTypeFolding(MachineFunction &MF, SPIRVGlobalRegistry *GR, MachineIRBuilder MIB) { MachineRegisterInfo &MRI = MF.getRegInfo(); for (MachineBasicBlock &MBB : MF) { for (MachineInstr &MI : MBB) { if (isTypeFoldingSupported(MI.getOpcode())) processInstr(MI, MIB, MRI, GR); } } for (MachineBasicBlock &MBB : MF) { for (MachineInstr &MI : MBB) { // We need to rewrite dst types for ASSIGN_TYPE instrs to be able // to perform tblgen'erated selection and we can't do that on Legalizer // as it operates on gMIR only. if (MI.getOpcode() != SPIRV::ASSIGN_TYPE) continue; Register SrcReg = MI.getOperand(1).getReg(); unsigned Opcode = MRI.getVRegDef(SrcReg)->getOpcode(); if (!isTypeFoldingSupported(Opcode)) continue; Register DstReg = MI.getOperand(0).getReg(); if (MRI.getType(DstReg).isVector()) MRI.setRegClass(DstReg, &SPIRV::IDRegClass); // Don't need to reset type of register holding constant and used in // G_ADDRSPACE_CAST, since it braaks legalizer. if (Opcode == TargetOpcode::G_CONSTANT && MRI.hasOneUse(DstReg)) { MachineInstr &UseMI = *MRI.use_instr_begin(DstReg); if (UseMI.getOpcode() == TargetOpcode::G_ADDRSPACE_CAST) continue; } MRI.setType(DstReg, LLT::scalar(32)); } } } // Find basic blocks of the switch and replace registers in spv_switch() by its // MBB equivalent. static void processSwitches(MachineFunction &MF, SPIRVGlobalRegistry *GR, MachineIRBuilder MIB) { DenseMap BB2MBB; SmallVector>> Switches; for (MachineBasicBlock &MBB : MF) { MachineRegisterInfo &MRI = MF.getRegInfo(); BB2MBB[MBB.getBasicBlock()] = &MBB; for (MachineInstr &MI : MBB) { if (!isSpvIntrinsic(MI, Intrinsic::spv_switch)) continue; // Calls to spv_switch intrinsics representing IR switches. SmallVector NewOps; for (unsigned i = 2; i < MI.getNumOperands(); ++i) { Register Reg = MI.getOperand(i).getReg(); if (i % 2 == 1) { MachineInstr *ConstInstr = getDefInstrMaybeConstant(Reg, &MRI); NewOps.push_back(ConstInstr); } else { MachineInstr *BuildMBB = MRI.getVRegDef(Reg); assert(BuildMBB && BuildMBB->getOpcode() == TargetOpcode::G_BLOCK_ADDR && BuildMBB->getOperand(1).isBlockAddress() && BuildMBB->getOperand(1).getBlockAddress()); NewOps.push_back(BuildMBB); } } Switches.push_back(std::make_pair(&MI, NewOps)); } } SmallPtrSet ToEraseMI; for (auto &SwIt : Switches) { MachineInstr &MI = *SwIt.first; SmallVector &Ins = SwIt.second; SmallVector NewOps; for (unsigned i = 0; i < Ins.size(); ++i) { if (Ins[i]->getOpcode() == TargetOpcode::G_BLOCK_ADDR) { BasicBlock *CaseBB = Ins[i]->getOperand(1).getBlockAddress()->getBasicBlock(); auto It = BB2MBB.find(CaseBB); if (It == BB2MBB.end()) report_fatal_error("cannot find a machine basic block by a basic " "block in a switch statement"); NewOps.push_back(MachineOperand::CreateMBB(It->second)); MI.getParent()->addSuccessor(It->second); ToEraseMI.insert(Ins[i]); } else { NewOps.push_back( MachineOperand::CreateCImm(Ins[i]->getOperand(1).getCImm())); } } for (unsigned i = MI.getNumOperands() - 1; i > 1; --i) MI.removeOperand(i); for (auto &MO : NewOps) MI.addOperand(MO); if (MachineInstr *Next = MI.getNextNode()) { if (isSpvIntrinsic(*Next, Intrinsic::spv_track_constant)) { ToEraseMI.insert(Next); Next = MI.getNextNode(); } if (Next && Next->getOpcode() == TargetOpcode::G_BRINDIRECT) ToEraseMI.insert(Next); } } for (MachineInstr *BlockAddrI : ToEraseMI) BlockAddrI->eraseFromParent(); } static bool isImplicitFallthrough(MachineBasicBlock &MBB) { if (MBB.empty()) return true; // Branching SPIR-V intrinsics are not detected by this generic method. // Thus, we can only trust negative result. if (!MBB.canFallThrough()) return false; // Otherwise, we must manually check if we have a SPIR-V intrinsic which // prevent an implicit fallthrough. for (MachineBasicBlock::reverse_iterator It = MBB.rbegin(), E = MBB.rend(); It != E; ++It) { if (isSpvIntrinsic(*It, Intrinsic::spv_switch)) return false; } return true; } static void removeImplicitFallthroughs(MachineFunction &MF, MachineIRBuilder MIB) { // It is valid for MachineBasicBlocks to not finish with a branch instruction. // In such cases, they will simply fallthrough their immediate successor. for (MachineBasicBlock &MBB : MF) { if (!isImplicitFallthrough(MBB)) continue; assert(std::distance(MBB.successors().begin(), MBB.successors().end()) == 1); MIB.setInsertPt(MBB, MBB.end()); MIB.buildBr(**MBB.successors().begin()); } } bool SPIRVPreLegalizer::runOnMachineFunction(MachineFunction &MF) { // Initialize the type registry. const SPIRVSubtarget &ST = MF.getSubtarget(); SPIRVGlobalRegistry *GR = ST.getSPIRVGlobalRegistry(); GR->setCurrentFunc(MF); MachineIRBuilder MIB(MF); addConstantsToTrack(MF, GR); foldConstantsIntoIntrinsics(MF); insertBitcasts(MF, GR, MIB); generateAssignInstrs(MF, GR, MIB); processSwitches(MF, GR, MIB); processInstrsWithTypeFolding(MF, GR, MIB); removeImplicitFallthroughs(MF, MIB); return true; } INITIALIZE_PASS(SPIRVPreLegalizer, DEBUG_TYPE, "SPIRV pre legalizer", false, false) char SPIRVPreLegalizer::ID = 0; FunctionPass *llvm::createSPIRVPreLegalizerPass() { return new SPIRVPreLegalizer(); }