| 1 | //===-- llvm/CodeGen/GlobalISel/CSEMIRBuilder.cpp - MIBuilder--*- C++ -*-==// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | /// \file |
| 9 | /// This file implements the CSEMIRBuilder class which CSEs as it builds |
| 10 | /// instructions. |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | // |
| 13 | |
| 14 | #include "llvm/CodeGen/GlobalISel/CSEMIRBuilder.h" |
| 15 | #include "llvm/CodeGen/GlobalISel/CSEInfo.h" |
| 16 | #include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h" |
| 17 | #include "llvm/CodeGen/GlobalISel/Utils.h" |
| 18 | #include "llvm/CodeGen/MachineInstrBuilder.h" |
| 19 | |
| 20 | using namespace llvm; |
| 21 | |
| 22 | bool CSEMIRBuilder::dominates(MachineBasicBlock::const_iterator A, |
| 23 | MachineBasicBlock::const_iterator B) const { |
| 24 | auto MBBEnd = getMBB().end(); |
| 25 | if (B == MBBEnd) |
| 26 | return true; |
| 27 | assert(A->getParent() == B->getParent() && |
| 28 | "Iterators should be in same block" ); |
| 29 | const MachineBasicBlock *BBA = A->getParent(); |
| 30 | MachineBasicBlock::const_iterator I = BBA->begin(); |
| 31 | for (; &*I != A && &*I != B; ++I) |
| 32 | ; |
| 33 | return &*I == A; |
| 34 | } |
| 35 | |
| 36 | MachineInstrBuilder |
| 37 | CSEMIRBuilder::getDominatingInstrForID(FoldingSetNodeID &ID, |
| 38 | void *&NodeInsertPos) { |
| 39 | GISelCSEInfo *CSEInfo = getCSEInfo(); |
| 40 | assert(CSEInfo && "Can't get here without setting CSEInfo" ); |
| 41 | MachineBasicBlock *CurMBB = &getMBB(); |
| 42 | MachineInstr *MI = |
| 43 | CSEInfo->getMachineInstrIfExists(ID, MBB: CurMBB, InsertPos&: NodeInsertPos); |
| 44 | if (MI) { |
| 45 | CSEInfo->countOpcodeHit(Opc: MI->getOpcode()); |
| 46 | auto CurrPos = getInsertPt(); |
| 47 | auto MII = MachineBasicBlock::iterator(MI); |
| 48 | if (MII == CurrPos) { |
| 49 | // Move the insert point ahead of the instruction so any future uses of |
| 50 | // this builder will have the def ready. |
| 51 | setInsertPt(MBB&: *CurMBB, II: std::next(x: MII)); |
| 52 | } else if (!dominates(A: MI, B: CurrPos)) { |
| 53 | // Update the spliced machineinstr's debug location by merging it with the |
| 54 | // debug location of the instruction at the insertion point. |
| 55 | auto Loc = DebugLoc::getMergedLocation(LocA: getDebugLoc(), LocB: MI->getDebugLoc()); |
| 56 | MI->setDebugLoc(Loc); |
| 57 | CurMBB->splice(Where: CurrPos, Other: CurMBB, From: MI); |
| 58 | } |
| 59 | return MachineInstrBuilder(getMF(), MI); |
| 60 | } |
| 61 | return MachineInstrBuilder(); |
| 62 | } |
| 63 | |
| 64 | bool CSEMIRBuilder::canPerformCSEForOpc(unsigned Opc) const { |
| 65 | const GISelCSEInfo *CSEInfo = getCSEInfo(); |
| 66 | if (!CSEInfo || !CSEInfo->shouldCSE(Opc)) |
| 67 | return false; |
| 68 | return true; |
| 69 | } |
| 70 | |
| 71 | void CSEMIRBuilder::profileDstOp(const DstOp &Op, |
| 72 | GISelInstProfileBuilder &B) const { |
| 73 | switch (Op.getDstOpKind()) { |
| 74 | case DstOp::DstType::Ty_RC: { |
| 75 | B.addNodeIDRegType(RC: Op.getRegClass()); |
| 76 | break; |
| 77 | } |
| 78 | case DstOp::DstType::Ty_Reg: { |
| 79 | // Regs can have LLT&(RB|RC). If those exist, profile them as well. |
| 80 | B.addNodeIDReg(Reg: Op.getReg()); |
| 81 | break; |
| 82 | } |
| 83 | case DstOp::DstType::Ty_LLT: { |
| 84 | B.addNodeIDRegType(Ty: Op.getLLTTy(MRI: *getMRI())); |
| 85 | break; |
| 86 | } |
| 87 | case DstOp::DstType::Ty_VRegAttrs: { |
| 88 | B.addNodeIDRegType(Op.getVRegAttrs()); |
| 89 | break; |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | void CSEMIRBuilder::profileSrcOp(const SrcOp &Op, |
| 95 | GISelInstProfileBuilder &B) const { |
| 96 | switch (Op.getSrcOpKind()) { |
| 97 | case SrcOp::SrcType::Ty_Imm: |
| 98 | B.addNodeIDImmediate(Imm: static_cast<int64_t>(Op.getImm())); |
| 99 | break; |
| 100 | case SrcOp::SrcType::Ty_Predicate: |
| 101 | B.addNodeIDImmediate(Imm: static_cast<int64_t>(Op.getPredicate())); |
| 102 | break; |
| 103 | default: |
| 104 | B.addNodeIDRegType(Op.getReg()); |
| 105 | break; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | void CSEMIRBuilder::profileMBBOpcode(GISelInstProfileBuilder &B, |
| 110 | unsigned Opc) const { |
| 111 | // First add the MBB (Local CSE). |
| 112 | B.addNodeIDMBB(MBB: &getMBB()); |
| 113 | // Then add the opcode. |
| 114 | B.addNodeIDOpcode(Opc); |
| 115 | } |
| 116 | |
| 117 | void CSEMIRBuilder::profileEverything(unsigned Opc, ArrayRef<DstOp> DstOps, |
| 118 | ArrayRef<SrcOp> SrcOps, |
| 119 | std::optional<unsigned> Flags, |
| 120 | GISelInstProfileBuilder &B) const { |
| 121 | |
| 122 | profileMBBOpcode(B, Opc); |
| 123 | // Then add the DstOps. |
| 124 | profileDstOps(Ops: DstOps, B); |
| 125 | // Then add the SrcOps. |
| 126 | profileSrcOps(Ops: SrcOps, B); |
| 127 | // Add Flags if passed in. |
| 128 | if (Flags) |
| 129 | B.addNodeIDFlag(Flag: *Flags); |
| 130 | } |
| 131 | |
| 132 | MachineInstrBuilder CSEMIRBuilder::memoizeMI(MachineInstrBuilder MIB, |
| 133 | void *NodeInsertPos) { |
| 134 | assert(canPerformCSEForOpc(MIB->getOpcode()) && |
| 135 | "Attempting to CSE illegal op" ); |
| 136 | MachineInstr *MIBInstr = MIB; |
| 137 | getCSEInfo()->insertInstr(MI: MIBInstr, InsertPos: NodeInsertPos); |
| 138 | return MIB; |
| 139 | } |
| 140 | |
| 141 | bool CSEMIRBuilder::checkCopyToDefsPossible(ArrayRef<DstOp> DstOps) { |
| 142 | if (DstOps.size() == 1) |
| 143 | return true; // always possible to emit copy to just 1 vreg. |
| 144 | |
| 145 | return llvm::all_of(Range&: DstOps, P: [](const DstOp &Op) { |
| 146 | DstOp::DstType DT = Op.getDstOpKind(); |
| 147 | return DT == DstOp::DstType::Ty_LLT || DT == DstOp::DstType::Ty_RC; |
| 148 | }); |
| 149 | } |
| 150 | |
| 151 | MachineInstrBuilder |
| 152 | CSEMIRBuilder::generateCopiesIfRequired(ArrayRef<DstOp> DstOps, |
| 153 | MachineInstrBuilder &MIB) { |
| 154 | assert(checkCopyToDefsPossible(DstOps) && |
| 155 | "Impossible return a single MIB with copies to multiple defs" ); |
| 156 | if (DstOps.size() == 1) { |
| 157 | const DstOp &Op = DstOps[0]; |
| 158 | if (Op.getDstOpKind() == DstOp::DstType::Ty_Reg) |
| 159 | return buildCopy(Res: Op.getReg(), Op: MIB.getReg(Idx: 0)); |
| 160 | } |
| 161 | |
| 162 | // If we didn't generate a copy then we're re-using an existing node directly |
| 163 | // instead of emitting any code. Merge the debug location we wanted to emit |
| 164 | // into the instruction we're CSE'ing with. Debug locations arent part of the |
| 165 | // profile so we don't need to recompute it. |
| 166 | if (getDebugLoc()) { |
| 167 | GISelChangeObserver *Observer = getState().Observer; |
| 168 | if (Observer) |
| 169 | Observer->changingInstr(MI&: *MIB); |
| 170 | MIB->setDebugLoc( |
| 171 | DebugLoc::getMergedLocation(LocA: MIB->getDebugLoc(), LocB: getDebugLoc())); |
| 172 | if (Observer) |
| 173 | Observer->changedInstr(MI&: *MIB); |
| 174 | } |
| 175 | |
| 176 | return MIB; |
| 177 | } |
| 178 | |
| 179 | MachineInstrBuilder CSEMIRBuilder::buildInstr(unsigned Opc, |
| 180 | ArrayRef<DstOp> DstOps, |
| 181 | ArrayRef<SrcOp> SrcOps, |
| 182 | std::optional<unsigned> Flag) { |
| 183 | switch (Opc) { |
| 184 | default: |
| 185 | break; |
| 186 | case TargetOpcode::G_ICMP: { |
| 187 | assert(SrcOps.size() == 3 && "Invalid sources" ); |
| 188 | assert(DstOps.size() == 1 && "Invalid dsts" ); |
| 189 | LLT SrcTy = SrcOps[1].getLLTTy(MRI: *getMRI()); |
| 190 | LLT DstTy = DstOps[0].getLLTTy(MRI: *getMRI()); |
| 191 | auto BoolExtOp = getBoolExtOp(IsVec: SrcTy.isVector(), IsFP: false); |
| 192 | |
| 193 | if (std::optional<SmallVector<APInt>> Cst = ConstantFoldICmp( |
| 194 | Pred: SrcOps[0].getPredicate(), Op1: SrcOps[1].getReg(), Op2: SrcOps[2].getReg(), |
| 195 | DstScalarSizeInBits: DstTy.getScalarSizeInBits(), ExtOp: BoolExtOp, MRI: *getMRI())) { |
| 196 | if (SrcTy.isVector()) |
| 197 | return buildBuildVectorConstant(Res: DstOps[0], Ops: *Cst); |
| 198 | return buildConstant(Res: DstOps[0], Val: Cst->front()); |
| 199 | } |
| 200 | break; |
| 201 | } |
| 202 | case TargetOpcode::G_ADD: |
| 203 | case TargetOpcode::G_PTR_ADD: |
| 204 | case TargetOpcode::G_AND: |
| 205 | case TargetOpcode::G_ASHR: |
| 206 | case TargetOpcode::G_LSHR: |
| 207 | case TargetOpcode::G_MUL: |
| 208 | case TargetOpcode::G_OR: |
| 209 | case TargetOpcode::G_SHL: |
| 210 | case TargetOpcode::G_SUB: |
| 211 | case TargetOpcode::G_XOR: |
| 212 | case TargetOpcode::G_UDIV: |
| 213 | case TargetOpcode::G_SDIV: |
| 214 | case TargetOpcode::G_UREM: |
| 215 | case TargetOpcode::G_SREM: |
| 216 | case TargetOpcode::G_SMIN: |
| 217 | case TargetOpcode::G_SMAX: |
| 218 | case TargetOpcode::G_UMIN: |
| 219 | case TargetOpcode::G_UMAX: { |
| 220 | // Try to constant fold these. |
| 221 | assert(SrcOps.size() == 2 && "Invalid sources" ); |
| 222 | assert(DstOps.size() == 1 && "Invalid dsts" ); |
| 223 | LLT SrcTy = SrcOps[0].getLLTTy(MRI: *getMRI()); |
| 224 | |
| 225 | if (Opc == TargetOpcode::G_PTR_ADD && |
| 226 | getDataLayout().isNonIntegralAddressSpace(AddrSpace: SrcTy.getAddressSpace())) |
| 227 | break; |
| 228 | |
| 229 | if (SrcTy.isVector()) { |
| 230 | // Try to constant fold vector constants. |
| 231 | SmallVector<APInt> VecCst = ConstantFoldVectorBinop( |
| 232 | Opcode: Opc, Op1: SrcOps[0].getReg(), Op2: SrcOps[1].getReg(), MRI: *getMRI()); |
| 233 | if (!VecCst.empty()) |
| 234 | return buildBuildVectorConstant(Res: DstOps[0], Ops: VecCst); |
| 235 | break; |
| 236 | } |
| 237 | |
| 238 | if (std::optional<APInt> Cst = ConstantFoldBinOp( |
| 239 | Opcode: Opc, Op1: SrcOps[0].getReg(), Op2: SrcOps[1].getReg(), MRI: *getMRI())) |
| 240 | return buildConstant(Res: DstOps[0], Val: *Cst); |
| 241 | break; |
| 242 | } |
| 243 | case TargetOpcode::G_FADD: |
| 244 | case TargetOpcode::G_FSUB: |
| 245 | case TargetOpcode::G_FMUL: |
| 246 | case TargetOpcode::G_FDIV: |
| 247 | case TargetOpcode::G_FREM: |
| 248 | case TargetOpcode::G_FMINNUM: |
| 249 | case TargetOpcode::G_FMAXNUM: |
| 250 | case TargetOpcode::G_FMINNUM_IEEE: |
| 251 | case TargetOpcode::G_FMAXNUM_IEEE: |
| 252 | case TargetOpcode::G_FMINIMUM: |
| 253 | case TargetOpcode::G_FMAXIMUM: |
| 254 | case TargetOpcode::G_FCOPYSIGN: { |
| 255 | // Try to constant fold these. |
| 256 | assert(SrcOps.size() == 2 && "Invalid sources" ); |
| 257 | assert(DstOps.size() == 1 && "Invalid dsts" ); |
| 258 | if (std::optional<APFloat> Cst = ConstantFoldFPBinOp( |
| 259 | Opcode: Opc, Op1: SrcOps[0].getReg(), Op2: SrcOps[1].getReg(), MRI: *getMRI())) |
| 260 | return buildFConstant(Res: DstOps[0], Val: *Cst); |
| 261 | break; |
| 262 | } |
| 263 | case TargetOpcode::G_SEXT_INREG: { |
| 264 | assert(DstOps.size() == 1 && "Invalid dst ops" ); |
| 265 | assert(SrcOps.size() == 2 && "Invalid src ops" ); |
| 266 | const DstOp &Dst = DstOps[0]; |
| 267 | const SrcOp &Src0 = SrcOps[0]; |
| 268 | const SrcOp &Src1 = SrcOps[1]; |
| 269 | if (auto MaybeCst = |
| 270 | ConstantFoldExtOp(Opcode: Opc, Op1: Src0.getReg(), Imm: Src1.getImm(), MRI: *getMRI())) |
| 271 | return buildConstant(Res: Dst, Val: *MaybeCst); |
| 272 | break; |
| 273 | } |
| 274 | case TargetOpcode::G_SITOFP: |
| 275 | case TargetOpcode::G_UITOFP: { |
| 276 | // Try to constant fold these. |
| 277 | assert(SrcOps.size() == 1 && "Invalid sources" ); |
| 278 | assert(DstOps.size() == 1 && "Invalid dsts" ); |
| 279 | if (std::optional<APFloat> Cst = ConstantFoldIntToFloat( |
| 280 | Opcode: Opc, DstTy: DstOps[0].getLLTTy(MRI: *getMRI()), Src: SrcOps[0].getReg(), MRI: *getMRI())) |
| 281 | return buildFConstant(Res: DstOps[0], Val: *Cst); |
| 282 | break; |
| 283 | } |
| 284 | case TargetOpcode::G_CTLZ: |
| 285 | case TargetOpcode::G_CTTZ: { |
| 286 | assert(SrcOps.size() == 1 && "Expected one source" ); |
| 287 | assert(DstOps.size() == 1 && "Expected one dest" ); |
| 288 | std::function<unsigned(APInt)> CB; |
| 289 | if (Opc == TargetOpcode::G_CTLZ) |
| 290 | CB = [](APInt V) -> unsigned { return V.countl_zero(); }; |
| 291 | else |
| 292 | CB = [](APInt V) -> unsigned { return V.countTrailingZeros(); }; |
| 293 | auto MaybeCsts = ConstantFoldCountZeros(Src: SrcOps[0].getReg(), MRI: *getMRI(), CB); |
| 294 | if (!MaybeCsts) |
| 295 | break; |
| 296 | if (MaybeCsts->size() == 1) |
| 297 | return buildConstant(Res: DstOps[0], Val: (*MaybeCsts)[0]); |
| 298 | // This was a vector constant. Build a G_BUILD_VECTOR for them. |
| 299 | SmallVector<Register> ConstantRegs; |
| 300 | LLT VecTy = DstOps[0].getLLTTy(MRI: *getMRI()); |
| 301 | for (unsigned Cst : *MaybeCsts) |
| 302 | ConstantRegs.emplace_back( |
| 303 | Args: buildConstant(Res: VecTy.getScalarType(), Val: Cst).getReg(Idx: 0)); |
| 304 | return buildBuildVector(Res: DstOps[0], Ops: ConstantRegs); |
| 305 | } |
| 306 | } |
| 307 | bool CanCopy = checkCopyToDefsPossible(DstOps); |
| 308 | if (!canPerformCSEForOpc(Opc)) |
| 309 | return MachineIRBuilder::buildInstr(Opc, DstOps, SrcOps, Flags: Flag); |
| 310 | // If we can CSE this instruction, but involves generating copies to multiple |
| 311 | // regs, give up. This frequently happens to UNMERGEs. |
| 312 | if (!CanCopy) { |
| 313 | auto MIB = MachineIRBuilder::buildInstr(Opc, DstOps, SrcOps, Flags: Flag); |
| 314 | // CSEInfo would have tracked this instruction. Remove it from the temporary |
| 315 | // insts. |
| 316 | getCSEInfo()->handleRemoveInst(MI: &*MIB); |
| 317 | return MIB; |
| 318 | } |
| 319 | FoldingSetNodeID ID; |
| 320 | GISelInstProfileBuilder ProfBuilder(ID, *getMRI()); |
| 321 | void *InsertPos = nullptr; |
| 322 | profileEverything(Opc, DstOps, SrcOps, Flags: Flag, B&: ProfBuilder); |
| 323 | MachineInstrBuilder MIB = getDominatingInstrForID(ID, NodeInsertPos&: InsertPos); |
| 324 | if (MIB) { |
| 325 | // Handle generating copies here. |
| 326 | return generateCopiesIfRequired(DstOps, MIB); |
| 327 | } |
| 328 | // This instruction does not exist in the CSEInfo. Build it and CSE it. |
| 329 | MachineInstrBuilder NewMIB = |
| 330 | MachineIRBuilder::buildInstr(Opc, DstOps, SrcOps, Flags: Flag); |
| 331 | return memoizeMI(MIB: NewMIB, NodeInsertPos: InsertPos); |
| 332 | } |
| 333 | |
| 334 | MachineInstrBuilder CSEMIRBuilder::buildConstant(const DstOp &Res, |
| 335 | const ConstantInt &Val) { |
| 336 | constexpr unsigned Opc = TargetOpcode::G_CONSTANT; |
| 337 | if (!canPerformCSEForOpc(Opc)) |
| 338 | return MachineIRBuilder::buildConstant(Res, Val); |
| 339 | |
| 340 | // For vectors, CSE the element only for now. |
| 341 | LLT Ty = Res.getLLTTy(MRI: *getMRI()); |
| 342 | if (Ty.isVector()) |
| 343 | return buildSplatBuildVector(Res, Src: buildConstant(Res: Ty.getElementType(), Val)); |
| 344 | |
| 345 | FoldingSetNodeID ID; |
| 346 | GISelInstProfileBuilder ProfBuilder(ID, *getMRI()); |
| 347 | void *InsertPos = nullptr; |
| 348 | profileMBBOpcode(B&: ProfBuilder, Opc); |
| 349 | profileDstOp(Op: Res, B&: ProfBuilder); |
| 350 | ProfBuilder.addNodeIDMachineOperand(MO: MachineOperand::CreateCImm(CI: &Val)); |
| 351 | MachineInstrBuilder MIB = getDominatingInstrForID(ID, NodeInsertPos&: InsertPos); |
| 352 | if (MIB) { |
| 353 | // Handle generating copies here. |
| 354 | return generateCopiesIfRequired(DstOps: {Res}, MIB); |
| 355 | } |
| 356 | |
| 357 | MachineInstrBuilder NewMIB = MachineIRBuilder::buildConstant(Res, Val); |
| 358 | return memoizeMI(MIB: NewMIB, NodeInsertPos: InsertPos); |
| 359 | } |
| 360 | |
| 361 | MachineInstrBuilder CSEMIRBuilder::buildFConstant(const DstOp &Res, |
| 362 | const ConstantFP &Val) { |
| 363 | constexpr unsigned Opc = TargetOpcode::G_FCONSTANT; |
| 364 | if (!canPerformCSEForOpc(Opc)) |
| 365 | return MachineIRBuilder::buildFConstant(Res, Val); |
| 366 | |
| 367 | // For vectors, CSE the element only for now. |
| 368 | LLT Ty = Res.getLLTTy(MRI: *getMRI()); |
| 369 | if (Ty.isVector()) |
| 370 | return buildSplatBuildVector(Res, Src: buildFConstant(Res: Ty.getElementType(), Val)); |
| 371 | |
| 372 | FoldingSetNodeID ID; |
| 373 | GISelInstProfileBuilder ProfBuilder(ID, *getMRI()); |
| 374 | void *InsertPos = nullptr; |
| 375 | profileMBBOpcode(B&: ProfBuilder, Opc); |
| 376 | profileDstOp(Op: Res, B&: ProfBuilder); |
| 377 | ProfBuilder.addNodeIDMachineOperand(MO: MachineOperand::CreateFPImm(CFP: &Val)); |
| 378 | MachineInstrBuilder MIB = getDominatingInstrForID(ID, NodeInsertPos&: InsertPos); |
| 379 | if (MIB) { |
| 380 | // Handle generating copies here. |
| 381 | return generateCopiesIfRequired(DstOps: {Res}, MIB); |
| 382 | } |
| 383 | MachineInstrBuilder NewMIB = MachineIRBuilder::buildFConstant(Res, Val); |
| 384 | return memoizeMI(MIB: NewMIB, NodeInsertPos: InsertPos); |
| 385 | } |
| 386 | |