| //===------- X86InsertPrefetch.cpp - Insert cache prefetch hints ----------===// | |
| // | |
| // 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 pass applies cache prefetch instructions based on a profile. The pass | |
| // assumes DiscriminateMemOps ran immediately before, to ensure debug info | |
| // matches the one used at profile generation time. The profile is encoded in | |
| // afdo format (text or binary). It contains prefetch hints recommendations. | |
| // Each recommendation is made in terms of debug info locations, a type (i.e. | |
| // nta, t{0|1|2}) and a delta. The debug info identifies an instruction with a | |
| // memory operand (see X86DiscriminateMemOps). The prefetch will be made for | |
| // a location at that memory operand + the delta specified in the | |
| // recommendation. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "X86.h" | |
| #include "X86InstrBuilder.h" | |
| #include "X86InstrInfo.h" | |
| #include "X86MachineFunctionInfo.h" | |
| #include "X86Subtarget.h" | |
| #include "llvm/CodeGen/MachineFunctionPass.h" | |
| #include "llvm/CodeGen/MachineModuleInfo.h" | |
| #include "llvm/IR/DebugInfoMetadata.h" | |
| #include "llvm/ProfileData/SampleProf.h" | |
| #include "llvm/ProfileData/SampleProfReader.h" | |
| #include "llvm/Transforms/IPO/SampleProfile.h" | |
| using namespace llvm; | |
| using namespace sampleprof; | |
| static cl::opt<std::string> | |
| PrefetchHintsFile("prefetch-hints-file", | |
| cl::desc("Path to the prefetch hints profile. See also " | |
| "-x86-discriminate-memops"), | |
| cl::Hidden); | |
| namespace { | |
| class X86InsertPrefetch : public MachineFunctionPass { | |
| void getAnalysisUsage(AnalysisUsage &AU) const override; | |
| bool doInitialization(Module &) override; | |
| bool runOnMachineFunction(MachineFunction &MF) override; | |
| struct PrefetchInfo { | |
| unsigned InstructionID; | |
| int64_t Delta; | |
| }; | |
| typedef SmallVectorImpl<PrefetchInfo> Prefetches; | |
| bool findPrefetchInfo(const FunctionSamples *Samples, const MachineInstr &MI, | |
| Prefetches &prefetches) const; | |
| public: | |
| static char ID; | |
| X86InsertPrefetch(const std::string &PrefetchHintsFilename); | |
| StringRef getPassName() const override { | |
| return "X86 Insert Cache Prefetches"; | |
| } | |
| private: | |
| std::string Filename; | |
| std::unique_ptr<SampleProfileReader> Reader; | |
| }; | |
| using PrefetchHints = SampleRecord::CallTargetMap; | |
| // Return any prefetching hints for the specified MachineInstruction. The hints | |
| // are returned as pairs (name, delta). | |
| ErrorOr<PrefetchHints> getPrefetchHints(const FunctionSamples *TopSamples, | |
| const MachineInstr &MI) { | |
| if (const auto &Loc = MI.getDebugLoc()) | |
| if (const auto *Samples = TopSamples->findFunctionSamples(Loc)) | |
| return Samples->findCallTargetMapAt(FunctionSamples::getOffset(Loc), | |
| Loc->getBaseDiscriminator()); | |
| return std::error_code(); | |
| } | |
| // The prefetch instruction can't take memory operands involving vector | |
| // registers. | |
| bool IsMemOpCompatibleWithPrefetch(const MachineInstr &MI, int Op) { | |
| Register BaseReg = MI.getOperand(Op + X86::AddrBaseReg).getReg(); | |
| Register IndexReg = MI.getOperand(Op + X86::AddrIndexReg).getReg(); | |
| return (BaseReg == 0 || | |
| X86MCRegisterClasses[X86::GR64RegClassID].contains(BaseReg) || | |
| X86MCRegisterClasses[X86::GR32RegClassID].contains(BaseReg)) && | |
| (IndexReg == 0 || | |
| X86MCRegisterClasses[X86::GR64RegClassID].contains(IndexReg) || | |
| X86MCRegisterClasses[X86::GR32RegClassID].contains(IndexReg)); | |
| } | |
| } // end anonymous namespace | |
| //===----------------------------------------------------------------------===// | |
| // Implementation | |
| //===----------------------------------------------------------------------===// | |
| char X86InsertPrefetch::ID = 0; | |
| X86InsertPrefetch::X86InsertPrefetch(const std::string &PrefetchHintsFilename) | |
| : MachineFunctionPass(ID), Filename(PrefetchHintsFilename) {} | |
| /// Return true if the provided MachineInstruction has cache prefetch hints. In | |
| /// that case, the prefetch hints are stored, in order, in the Prefetches | |
| /// vector. | |
| bool X86InsertPrefetch::findPrefetchInfo(const FunctionSamples *TopSamples, | |
| const MachineInstr &MI, | |
| Prefetches &Prefetches) const { | |
| assert(Prefetches.empty() && | |
| "Expected caller passed empty PrefetchInfo vector."); | |
| static constexpr std::pair<StringLiteral, unsigned> HintTypes[] = { | |
| {"_nta_", X86::PREFETCHNTA}, | |
| {"_t0_", X86::PREFETCHT0}, | |
| {"_t1_", X86::PREFETCHT1}, | |
| {"_t2_", X86::PREFETCHT2}, | |
| }; | |
| static const char *SerializedPrefetchPrefix = "__prefetch"; | |
| const ErrorOr<PrefetchHints> T = getPrefetchHints(TopSamples, MI); | |
| if (!T) | |
| return false; | |
| int16_t max_index = -1; | |
| // Convert serialized prefetch hints into PrefetchInfo objects, and populate | |
| // the Prefetches vector. | |
| for (const auto &S_V : *T) { | |
| StringRef Name = S_V.getKey(); | |
| if (Name.consume_front(SerializedPrefetchPrefix)) { | |
| int64_t D = static_cast<int64_t>(S_V.second); | |
| unsigned IID = 0; | |
| for (const auto &HintType : HintTypes) { | |
| if (Name.startswith(HintType.first)) { | |
| Name = Name.drop_front(HintType.first.size()); | |
| IID = HintType.second; | |
| break; | |
| } | |
| } | |
| if (IID == 0) | |
| return false; | |
| uint8_t index = 0; | |
| Name.consumeInteger(10, index); | |
| if (index >= Prefetches.size()) | |
| Prefetches.resize(index + 1); | |
| Prefetches[index] = {IID, D}; | |
| max_index = std::max(max_index, static_cast<int16_t>(index)); | |
| } | |
| } | |
| assert(max_index + 1 >= 0 && | |
| "Possible overflow: max_index + 1 should be positive."); | |
| assert(static_cast<size_t>(max_index + 1) == Prefetches.size() && | |
| "The number of prefetch hints received should match the number of " | |
| "PrefetchInfo objects returned"); | |
| return !Prefetches.empty(); | |
| } | |
| bool X86InsertPrefetch::doInitialization(Module &M) { | |
| if (Filename.empty()) | |
| return false; | |
| LLVMContext &Ctx = M.getContext(); | |
| ErrorOr<std::unique_ptr<SampleProfileReader>> ReaderOrErr = | |
| SampleProfileReader::create(Filename, Ctx); | |
| if (std::error_code EC = ReaderOrErr.getError()) { | |
| std::string Msg = "Could not open profile: " + EC.message(); | |
| Ctx.diagnose(DiagnosticInfoSampleProfile(Filename, Msg, | |
| DiagnosticSeverity::DS_Warning)); | |
| return false; | |
| } | |
| Reader = std::move(ReaderOrErr.get()); | |
| Reader->read(); | |
| return true; | |
| } | |
| void X86InsertPrefetch::getAnalysisUsage(AnalysisUsage &AU) const { | |
| AU.setPreservesAll(); | |
| MachineFunctionPass::getAnalysisUsage(AU); | |
| } | |
| bool X86InsertPrefetch::runOnMachineFunction(MachineFunction &MF) { | |
| if (!Reader) | |
| return false; | |
| const FunctionSamples *Samples = Reader->getSamplesFor(MF.getFunction()); | |
| if (!Samples) | |
| return false; | |
| bool Changed = false; | |
| const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo(); | |
| SmallVector<PrefetchInfo, 4> Prefetches; | |
| for (auto &MBB : MF) { | |
| for (auto MI = MBB.instr_begin(); MI != MBB.instr_end();) { | |
| auto Current = MI; | |
| ++MI; | |
| int Offset = X86II::getMemoryOperandNo(Current->getDesc().TSFlags); | |
| if (Offset < 0) | |
| continue; | |
| unsigned Bias = X86II::getOperandBias(Current->getDesc()); | |
| int MemOpOffset = Offset + Bias; | |
| // FIXME(mtrofin): ORE message when the recommendation cannot be taken. | |
| if (!IsMemOpCompatibleWithPrefetch(*Current, MemOpOffset)) | |
| continue; | |
| Prefetches.clear(); | |
| if (!findPrefetchInfo(Samples, *Current, Prefetches)) | |
| continue; | |
| assert(!Prefetches.empty() && | |
| "The Prefetches vector should contain at least a value if " | |
| "findPrefetchInfo returned true."); | |
| for (auto &PrefInfo : Prefetches) { | |
| unsigned PFetchInstrID = PrefInfo.InstructionID; | |
| int64_t Delta = PrefInfo.Delta; | |
| const MCInstrDesc &Desc = TII->get(PFetchInstrID); | |
| MachineInstr *PFetch = | |
| MF.CreateMachineInstr(Desc, Current->getDebugLoc(), true); | |
| MachineInstrBuilder MIB(MF, PFetch); | |
| static_assert(X86::AddrBaseReg == 0 && X86::AddrScaleAmt == 1 && | |
| X86::AddrIndexReg == 2 && X86::AddrDisp == 3 && | |
| X86::AddrSegmentReg == 4, | |
| "Unexpected change in X86 operand offset order."); | |
| // This assumes X86::AddBaseReg = 0, {...}ScaleAmt = 1, etc. | |
| // FIXME(mtrofin): consider adding a: | |
| // MachineInstrBuilder::set(unsigned offset, op). | |
| MIB.addReg(Current->getOperand(MemOpOffset + X86::AddrBaseReg).getReg()) | |
| .addImm( | |
| Current->getOperand(MemOpOffset + X86::AddrScaleAmt).getImm()) | |
| .addReg( | |
| Current->getOperand(MemOpOffset + X86::AddrIndexReg).getReg()) | |
| .addImm(Current->getOperand(MemOpOffset + X86::AddrDisp).getImm() + | |
| Delta) | |
| .addReg(Current->getOperand(MemOpOffset + X86::AddrSegmentReg) | |
| .getReg()); | |
| if (!Current->memoperands_empty()) { | |
| MachineMemOperand *CurrentOp = *(Current->memoperands_begin()); | |
| MIB.addMemOperand(MF.getMachineMemOperand( | |
| CurrentOp, CurrentOp->getOffset() + Delta, CurrentOp->getSize())); | |
| } | |
| // Insert before Current. This is because Current may clobber some of | |
| // the registers used to describe the input memory operand. | |
| MBB.insert(Current, PFetch); | |
| Changed = true; | |
| } | |
| } | |
| } | |
| return Changed; | |
| } | |
| FunctionPass *llvm::createX86InsertPrefetchPass() { | |
| return new X86InsertPrefetch(PrefetchHintsFile); | |
| } |