//===- IROutliner.cpp -- Outline Similar Regions ----------------*- 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 // //===----------------------------------------------------------------------===// /// /// \file // Implementation for the IROutliner which is used by the IROutliner Pass. // //===----------------------------------------------------------------------===// #include "llvm/Transforms/IPO/IROutliner.h" #include "llvm/Analysis/IRSimilarityIdentifier.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/PassManager.h" #include "llvm/InitializePasses.h" #include "llvm/Pass.h" #include "llvm/Support/CommandLine.h" #include "llvm/Transforms/IPO.h" #include #include #include #define DEBUG_TYPE "iroutliner" using namespace llvm; using namespace IRSimilarity; /// The OutlinableGroup holds all the overarching information for outlining /// a set of regions that are structurally similar to one another, such as the /// types of the overall function, the output blocks, the sets of stores needed /// and a list of the different regions. This information is used in the /// deduplication of extracted regions with the same structure. struct OutlinableGroup { /// The sections that could be outlined std::vector Regions; /// Flag for whether we should not consider this group of OutlinableRegions /// for extraction. bool IgnoreGroup = false; }; /// Move the contents of \p SourceBB to before the last instruction of \p /// TargetBB. /// \param SourceBB - the BasicBlock to pull Instructions from. /// \param TargetBB - the BasicBlock to put Instruction into. static void moveBBContents(BasicBlock &SourceBB, BasicBlock &TargetBB) { BasicBlock::iterator BBCurr, BBEnd, BBNext; for (BBCurr = SourceBB.begin(), BBEnd = SourceBB.end(); BBCurr != BBEnd; BBCurr = BBNext) { BBNext = std::next(BBCurr); BBCurr->moveBefore(TargetBB, TargetBB.end()); } } void OutlinableRegion::splitCandidate() { assert(!CandidateSplit && "Candidate already split!"); Instruction *StartInst = (*Candidate->begin()).Inst; Instruction *EndInst = (*Candidate->end()).Inst; assert(StartInst && EndInst && "Expected a start and end instruction?"); StartBB = StartInst->getParent(); PrevBB = StartBB; // The basic block gets split like so: // block: block: // inst1 inst1 // inst2 inst2 // region1 br block_to_outline // region2 block_to_outline: // region3 -> region1 // region4 region2 // inst3 region3 // inst4 region4 // br block_after_outline // block_after_outline: // inst3 // inst4 std::string OriginalName = PrevBB->getName().str(); StartBB = PrevBB->splitBasicBlock(StartInst, OriginalName + "_to_outline"); // This is the case for the inner block since we do not have to include // multiple blocks. EndBB = StartBB; FollowBB = EndBB->splitBasicBlock(EndInst, OriginalName + "_after_outline"); CandidateSplit = true; } void OutlinableRegion::reattachCandidate() { assert(CandidateSplit && "Candidate is not split!"); // The basic block gets reattached like so: // block: block: // inst1 inst1 // inst2 inst2 // br block_to_outline region1 // block_to_outline: -> region2 // region1 region3 // region2 region4 // region3 inst3 // region4 inst4 // br block_after_outline // block_after_outline: // inst3 // inst4 assert(StartBB != nullptr && "StartBB for Candidate is not defined!"); assert(FollowBB != nullptr && "StartBB for Candidate is not defined!"); // StartBB should only have one predecessor since we put an unconditional // branch at the end of PrevBB when we split the BasicBlock. PrevBB = StartBB->getSinglePredecessor(); assert(PrevBB != nullptr && "No Predecessor for the region start basic block!"); assert(PrevBB->getTerminator() && "Terminator removed from PrevBB!"); assert(EndBB->getTerminator() && "Terminator removed from EndBB!"); PrevBB->getTerminator()->eraseFromParent(); EndBB->getTerminator()->eraseFromParent(); moveBBContents(*StartBB, *PrevBB); BasicBlock *PlacementBB = PrevBB; if (StartBB != EndBB) PlacementBB = EndBB; moveBBContents(*FollowBB, *PlacementBB); PrevBB->replaceSuccessorsPhiUsesWith(StartBB, PrevBB); PrevBB->replaceSuccessorsPhiUsesWith(FollowBB, PlacementBB); StartBB->eraseFromParent(); FollowBB->eraseFromParent(); // Make sure to save changes back to the StartBB. StartBB = PrevBB; EndBB = nullptr; PrevBB = nullptr; FollowBB = nullptr; CandidateSplit = false; } void IROutliner::pruneIncompatibleRegions( std::vector &CandidateVec, OutlinableGroup &CurrentGroup) { bool PreviouslyOutlined; // Sort from beginning to end, so the IRSimilarityCandidates are in order. stable_sort(CandidateVec, [](const IRSimilarityCandidate &LHS, const IRSimilarityCandidate &RHS) { return LHS.getStartIdx() < RHS.getStartIdx(); }); unsigned CurrentEndIdx = 0; for (IRSimilarityCandidate &IRSC : CandidateVec) { PreviouslyOutlined = false; unsigned StartIdx = IRSC.getStartIdx(); unsigned EndIdx = IRSC.getEndIdx(); for (unsigned Idx = StartIdx; Idx <= EndIdx; Idx++) if (Outlined.find(Idx) != Outlined.end()) { PreviouslyOutlined = true; break; } if (PreviouslyOutlined) continue; // TODO: If in the future we can outline across BasicBlocks, we will need to // check all BasicBlocks contained in the region. if (IRSC.getStartBB()->hasAddressTaken()) continue; // Greedily prune out any regions that will overlap with already chosen // regions. if (CurrentEndIdx != 0 && StartIdx <= CurrentEndIdx) continue; bool BadInst = any_of(IRSC, [this](IRInstructionData &ID) { return !this->InstructionClassifier.visit(ID.Inst); }); if (BadInst) continue; OutlinableRegion *OS = new (RegionAllocator.Allocate()) OutlinableRegion(IRSC, CurrentGroup); CurrentGroup.Regions.push_back(OS); CurrentEndIdx = EndIdx; } } bool IROutliner::extractSection(OutlinableRegion &Region) { assert(Region.StartBB != nullptr && "StartBB for the OutlinableRegion is nullptr!"); assert(Region.FollowBB != nullptr && "StartBB for the OutlinableRegion is nullptr!"); Function *OrigF = Region.StartBB->getParent(); CodeExtractorAnalysisCache CEAC(*OrigF); Region.ExtractedFunction = Region.CE->extractCodeRegion(CEAC); // If the extraction was successful, find the BasicBlock, and reassign the // OutlinableRegion blocks if (!Region.ExtractedFunction) { LLVM_DEBUG(dbgs() << "CodeExtractor failed to outline " << Region.StartBB << "\n"); Region.reattachCandidate(); return false; } BasicBlock *RewrittenBB = Region.FollowBB->getSinglePredecessor(); Region.StartBB = RewrittenBB; Region.EndBB = RewrittenBB; // The sequences of outlinable regions has now changed. We must fix the // IRInstructionDataList for consistency. Although they may not be illegal // instructions, they should not be compared with anything else as they // should not be outlined in this round. So marking these as illegal is // allowed. IRInstructionDataList *IDL = Region.Candidate->front()->IDL; Instruction *BeginRewritten = &*RewrittenBB->begin(); Instruction *EndRewritten = &*RewrittenBB->begin(); Region.NewFront = new (InstDataAllocator.Allocate()) IRInstructionData( *BeginRewritten, InstructionClassifier.visit(*BeginRewritten), *IDL); Region.NewBack = new (InstDataAllocator.Allocate()) IRInstructionData( *EndRewritten, InstructionClassifier.visit(*EndRewritten), *IDL); // Insert the first IRInstructionData of the new region in front of the // first IRInstructionData of the IRSimilarityCandidate. IDL->insert(Region.Candidate->begin(), *Region.NewFront); // Insert the first IRInstructionData of the new region after the // last IRInstructionData of the IRSimilarityCandidate. IDL->insert(Region.Candidate->end(), *Region.NewBack); // Remove the IRInstructionData from the IRSimilarityCandidate. IDL->erase(Region.Candidate->begin(), std::prev(Region.Candidate->end())); assert(RewrittenBB != nullptr && "Could not find a predecessor after extraction!"); // Iterate over the new set of instructions to find the new call // instruction. for (Instruction &I : *RewrittenBB) if (CallInst *CI = dyn_cast(&I)) if (Region.ExtractedFunction == CI->getCalledFunction()) { Region.Call = CI; break; } Region.reattachCandidate(); return true; } unsigned IROutliner::doOutline(Module &M) { // Find the possibile similarity sections. IRSimilarityIdentifier &Identifier = getIRSI(M); SimilarityGroupList &SimilarityCandidates = *Identifier.getSimilarity(); // Sort them by size of extracted sections unsigned OutlinedFunctionNum = 0; // If we only have one SimilarityGroup in SimilarityCandidates, we do not have // to sort them by the potential number of instructions to be outlined if (SimilarityCandidates.size() > 1) llvm::stable_sort(SimilarityCandidates, [](const std::vector &LHS, const std::vector &RHS) { return LHS[0].getLength() * LHS.size() > RHS[0].getLength() * RHS.size(); }); // Iterate over the possible sets of similarity. for (SimilarityGroup &CandidateVec : SimilarityCandidates) { OutlinableGroup CurrentGroup; // Remove entries that were previously outlined pruneIncompatibleRegions(CandidateVec, CurrentGroup); // We pruned the number of regions to 0 to 1, meaning that it's not worth // trying to outlined since there is no compatible similar instance of this // code. if (CurrentGroup.Regions.size() < 2) continue; // Create a CodeExtractor for each outlinable region. for (OutlinableRegion *OS : CurrentGroup.Regions) { // Break the outlinable region out of its parent BasicBlock into its own // BasicBlocks (see function implementation). OS->splitCandidate(); std::vector BE = {OS->StartBB}; OS->CE = new (ExtractorAllocator.Allocate()) CodeExtractor(BE, nullptr, false, nullptr, nullptr, nullptr, false, false, "outlined"); } // Create functions out of all the sections, and mark them as outlined std::vector OutlinedRegions; for (OutlinableRegion *OS : CurrentGroup.Regions) { OutlinedFunctionNum++; bool FunctionOutlined = extractSection(*OS); if (FunctionOutlined) { unsigned StartIdx = OS->Candidate->getStartIdx(); unsigned EndIdx = OS->Candidate->getEndIdx(); for (unsigned Idx = StartIdx; Idx <= EndIdx; Idx++) Outlined.insert(Idx); OutlinedRegions.push_back(OS); } } CurrentGroup.Regions = std::move(OutlinedRegions); } return OutlinedFunctionNum; } bool IROutliner::run(Module &M) { return doOutline(M) > 0; } // Pass Manager Boilerplate class IROutlinerLegacyPass : public ModulePass { public: static char ID; IROutlinerLegacyPass() : ModulePass(ID) { initializeIROutlinerLegacyPassPass(*PassRegistry::getPassRegistry()); } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired(); AU.addRequired(); } bool runOnModule(Module &M) override; }; bool IROutlinerLegacyPass::runOnModule(Module &M) { if (skipModule(M)) return false; auto GTTI = [this](Function &F) -> TargetTransformInfo & { return this->getAnalysis().getTTI(F); }; auto GIRSI = [this](Module &) -> IRSimilarityIdentifier & { return this->getAnalysis().getIRSI(); }; return IROutliner(GTTI, GIRSI).run(M); } PreservedAnalyses IROutlinerPass::run(Module &M, ModuleAnalysisManager &AM) { auto &FAM = AM.getResult(M).getManager(); std::function GTTI = [&FAM](Function &F) -> TargetTransformInfo & { return FAM.getResult(F); }; std::function GIRSI = [&AM](Module &M) -> IRSimilarityIdentifier & { return AM.getResult(M); }; if (IROutliner(GTTI, GIRSI).run(M)) return PreservedAnalyses::none(); return PreservedAnalyses::all(); } char IROutlinerLegacyPass::ID = 0; INITIALIZE_PASS_BEGIN(IROutlinerLegacyPass, "iroutliner", "IR Outliner", false, false) INITIALIZE_PASS_DEPENDENCY(IRSimilarityIdentifierWrapperPass) INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass) INITIALIZE_PASS_END(IROutlinerLegacyPass, "iroutliner", "IR Outliner", false, false) ModulePass *llvm::createIROutlinerPass() { return new IROutlinerLegacyPass(); }