Import UnitLoopInfo and UnitLICM
[?]
8Cqo1QjSCQ3F66Vh3nan9N8dv1MjQSAEz8RNwt2cTacK
May 2, 2024, 11:15 PM
56LYABQH3HNWTV4MWQ2IWW7LEIYKMOW4ZVTRXKOSQID4JTQ5A35ACDependencies
- [2]
7OVOGA6QImport LLVM @ 15.0.7
Change contents
- file addition: UnitLICM.cpp[2.509877339]
#include <cassert>#include <utility>#include <functional>#include <iterator>#include <vector>#include <ranges>#include "llvm/Support/raw_ostream.h"#include "llvm/Support/Debug.h"#include "llvm/Support/Casting.h"#include "llvm/ADT/SmallVector.h"#include "llvm/ADT/DenseMap.h"#include "llvm/ADT/Statistic.h"#include "llvm/IR/Value.h"#include "llvm/IR/Function.h"#include "llvm/IR/Instruction.h"#include "llvm/IR/Instructions.h"#include "llvm/IR/PatternMatch.h"#include "llvm/IR/Dominators.h"#include "llvm/Analysis/AliasAnalysis.h"#include "llvm/Analysis/AliasSetTracker.h"#include "llvm/Analysis/ValueTracking.h"#include "llvm/Transforms/UnitLICM.h"#include "llvm/Analysis/UnitLoopInfo.h"#define DEBUG_TYPE "unit-licm"STATISTIC(NumInstsHoisted, "Number of instructions hoisted");STATISTIC(NumStoresHoisted, "Number of stores hoisted");STATISTIC(NumLoadsHoisted, "Number of loads hoisted");STATISTIC(NumComputationalHoisted, "Number of computational instructions (excluding casts and GEPs) hoisted");STATISTIC(NumVisited, "Number of instructions considered for hoisting");using namespace llvm;using namespace ece479k;/// Since all symbols are exported from the SO, we try to mark all symbols local/// to this translation unit to improve diagnostics and improve performance./// Likewise, we try to keep functions pure.////// To reduce confusion when describing LICM, we classify two types of "invariants":/// - Invariant: a Value that is already invariant to the loop - it is outside the loop or not an Instruction/// - Hoistable: a Value that can be hoisted outside the loop to become an invariant////// (Un)Hoistable detection is implemented as DFS on every use-def chain in the loop,/// stopping if we end up at an invariant or condition that prevents hoisting,/// propagating the result back up the chain to every Value visited.////// This is implemented as a series of tail-recursive calls to check each/// instruction for any disqualifying cases or an invariant and then recursively/// on each of its operands (the part that makes it a DFS).////// Optimization misses are documented in debug output. For meaningful names,/// make sure to run the `instnamer` pass before running `unit-licm`./// Example output:/// UnitLICM: Can't hoist instruction: Depends on unsupported phi `p.0`: %idxprom = sext i32 %p.0 to i64/// UnitLICM: Can't hoist instruction: Depends on not-hoistable sext `idxprom`: %arrayidx18 = getelementptr inbounds [8 x [3 x double]], ptr %position, i64 0, i64 %idxprom/// UnitLICM: Can't hoist instruction: Depends on not-hoistable getelementptr `arrayidx18`: %arraydecay19 = getelementptr inbounds [3 x double], ptr %arrayidx18, i64 0, i64 0static bool is_supported(Instruction const&) __attribute((pure));static bool is_computational(Instruction const&) __attribute((pure));namespace {struct HoistArgs {UnitLoopInfo const& loop;DenseMap<Instruction const*, bool> cache;DominatorTree const& DT;};}static bool can_hoist(Instruction const&, HoistArgs &) __attribute((pure));static void hoist(Instruction&, HoistArgs &);// ReSharper disable once CppMemberFunctionMayBeStaticPreservedAnalyses UnitLICM::run(Function &F, FunctionAnalysisManager &FAM) // NOLINT(*-convert-member-functions-to-static){for (auto const& loop: FAM.getResult<UnitLoopAnalysis>(F)) {auto hoist_args = HoistArgs{loop,DenseMap<Instruction const*, bool>(32),FAM.getResult<DominatorTreeAnalysis>(F)};hoist_args.cache.clear();auto const is_hoistable = [&loop, &hoist_args](Instruction const& inst) {return is_supported(inst) && can_hoist(inst, hoist_args);};// Hoist everything we canfor (auto i: loop.instructions(is_hoistable)){hoist(*i, hoist_args);if (isa<LoadInst>(i))++NumLoadsHoisted;else if (isa<StoreInst>(i))++NumStoresHoisted;else if (is_computational(*i))++NumComputationalHoisted;++NumInstsHoisted;}}// Invalidate analyses (we fucked the code up)return PreservedAnalyses::none();}// This switch is inlined instead of chaining isXXX calls because it's easier to see what's going on// and we get slightly better codegen (practically it doesn't matter)static bool is_supported(Instruction const& inst){switch (inst.getOpcode()) {default:return false;// unary operatorcase Instruction::FNeg:// binary operatorcase Instruction::Add:case Instruction::FAdd:case Instruction::Sub:case Instruction::FSub:case Instruction::Mul:case Instruction::FMul:case Instruction::UDiv:case Instruction::SDiv:case Instruction::FDiv:case Instruction::URem:case Instruction::SRem:case Instruction::FRem:// logical operatorcase Instruction::Shl:case Instruction::LShr:case Instruction::AShr:case Instruction::And:case Instruction::Or:case Instruction::Xor:// memory operatorcase Instruction::Load:case Instruction::Store:case Instruction::GetElementPtr:// cast operatorcase Instruction::Trunc:case Instruction::ZExt:case Instruction::SExt:case Instruction::FPToUI:case Instruction::FPToSI:case Instruction::UIToFP:case Instruction::SIToFP:case Instruction::FPTrunc:case Instruction::FPExt:case Instruction::PtrToInt:case Instruction::IntToPtr:case Instruction::BitCast:case Instruction::AddrSpaceCast:// misc operatorcase Instruction::Select:case Instruction::ICmp:case Instruction::FCmp:return true;}}static bool is_computational(Instruction const& inst){switch (inst.getOpcode()) {default:return false;// unary operatorcase Instruction::FNeg:// binary operatorcase Instruction::Add:case Instruction::FAdd:case Instruction::Sub:case Instruction::FSub:case Instruction::Mul:case Instruction::FMul:case Instruction::UDiv:case Instruction::SDiv:case Instruction::FDiv:case Instruction::URem:case Instruction::SRem:case Instruction::FRem:// logical operatorcase Instruction::Shl:case Instruction::LShr:case Instruction::AShr:case Instruction::And:case Instruction::Or:case Instruction::Xor:// misc operatorcase Instruction::Select:case Instruction::ICmp:case Instruction::FCmp:return true;}}static void hoist(Instruction& inst, HoistArgs &args){assert(!args.loop.is_invariant(inst) && "Cannot hoist variant");auto* insertion_point = const_cast<Instruction *>(args.loop.preheader->getTerminator());inst.moveBefore(insertion_point);assert(args.loop.is_invariant(inst) && "Hoist did not make invariant");}// These are called in a tail-recursive (ish) mannerstatic bool can_hoist_impl(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist(Value const&, HoistArgs &) __attribute((pure));static bool can_hoist_udiv(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist_sdiv(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist_store(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist_load(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist_load_impl(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist_sidefx(Instruction const&, HoistArgs&) __attribute((pure));static bool can_hoist_operands(Instruction const&, HoistArgs &) __attribute((pure));static bool can_hoist(Instruction const& inst, HoistArgs &args){if (args.loop.is_invariant(inst))return true;if (!is_supported(inst)) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist instruction: Depends on unsupported " << inst.getOpcodeName() << " `" << inst.getName() << "`: ");return false;}auto const it = args.cache.find(&inst);if (it != args.cache.end()) {LLVM_DEBUG(if (!it->second)dbgs() << "UnitLICM: Can't hoist instruction: Depends on not-hoistable " << inst.getOpcodeName() << " `" << inst.getName() << "`: ");return it->second;}bool const dfs = can_hoist_impl(inst, args);args.cache.insert({&inst, dfs});return dfs;}static bool can_hoist(Value const& val, HoistArgs &args){if (auto const* inst = dyn_cast<Instruction>(&val))return can_hoist(*inst, args);return true;}/// This is effectively a specialization of isSafeToSpeculativelyExecute for our case:/// - We support stores/// - We don't bail directly if an instruction has sidefx - we check if it dominates all exits to try and hoist it anyway/// - We don't check for instructions we don't support////// This will dispatch to special cases for instructions that can have side effects/// or cause the use-def chain to be marked as not-hoistable. Otherwise, this/// will directly dispatch to the next step of the DFSstatic bool can_hoist_impl(Instruction const& inst, HoistArgs &args){assert(is_supported(inst) && "Instruction is not supported");++NumVisited;switch (inst.getOpcode()) {default: // Instruction doesn't have edge casesreturn can_hoist_operands(inst, args);case Instruction::Load: // Need to do alias analysisreturn can_hoist_load(inst, args);case Instruction::Store: // This one always has sidefx, and we need to do alias analysisreturn can_hoist_store(inst, args);case Instruction::UDiv:case Instruction::URem: // Division by zero can throwreturn can_hoist_udiv(inst, args);case Instruction::SDiv:case Instruction::SRem: // Division by zero or of INT_MIN can throwreturn can_hoist_sdiv(inst, args);}}static bool can_hoist_sdiv(Instruction const& inst, HoistArgs &args){// code stolen from isSafeToSpeculativelyExecuteusing namespace llvm::PatternMatch;// x / y is undefined if y == 0 or x == INT_MIN and y == -1const APInt *Numerator, *Denominator;if (!match(inst.getOperand(1), m_APInt(Denominator))) {// LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist " << inst.getOpcodeName() << ": Undefined denominator " << inst.getOperand(1) << ":" << inst << "\n");return can_hoist_sidefx(inst, args);}// We cannot hoist this division if the denominator is 0.if (*Denominator == 0) {// LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist " << inst.getOpcodeName() << ": Division by zero:" << inst << "\n");return can_hoist_sidefx(inst, args);}// It's safe to hoist if the denominator is not 0 or -1.if (!Denominator->isAllOnes()) {return can_hoist_operands(inst, args);}// At this point we know that the denominator is -1. It is safe to hoist as// long we know that the numerator is not INT_MIN.if (match(inst.getOperand(0), m_APInt(Numerator))) {if (Numerator->isMinSignedValue()) {// LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist " << inst.getOpcodeName() << ": Division of INT_MIN by -1:" << inst << "\n");return can_hoist_sidefx(inst, args);}return can_hoist_operands(inst, args);}// The numerator *might* be MinSignedValue.// LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist " << inst.getOpcodeName() << ": Division of INT_MIN( " << Numerator << " ) by -1:" << inst << "\n");return can_hoist_sidefx(inst, args);}static bool can_hoist_udiv(Instruction const& inst, HoistArgs &args){// code stolen from isSafeToSpeculativelyExecuteusing namespace llvm::PatternMatch;// x / y is undefined if y == 0.const APInt *V;if (match(inst.getOperand(1), m_APInt(V))) {if (!*V) {// LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist " << inst.getOpcodeName() << ": Division by zero:" << inst << "\n");return can_hoist_sidefx(inst, args);}return can_hoist_operands(inst, args);}return false;}static bool can_hoist_load(Instruction const& inst, HoistArgs &args){// code stolen from isSafeToSpeculativelyExecuteconst auto *LI = dyn_cast<LoadInst>(&inst);if (!LI) {LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected load): " << inst << "\n");return false;}if (mustSuppressSpeculation(*LI)) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist load: Suppressed by sanitizer:" << inst << "\n");return false;}return can_hoist_load_impl(inst, args);}static bool can_hoist_load_impl(Instruction const& inst, HoistArgs &args){auto const *LI = dyn_cast<LoadInst>(&inst);if (!LI) {LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected load): " << inst << "\n");return false;}auto const& aa = args.loop.alias_for(LI);// If the load always reads the same mem loc, we can hoist itif (aa.isMod()) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist load: Load aliases modified memory:" << inst << "\n");return false;}return can_hoist_operands(inst, args);}static bool can_hoist_store(Instruction const& inst, HoistArgs &args){auto const *SI = dyn_cast<StoreInst>(&inst);if (!SI) {LLVM_DEBUG(dbgs() << "UnitLICM: Invalid instruction (expected store):\t" << inst << "\n");return false;}// Volatile stores may not return, so we can't hoist themif (SI->isVolatile())return false;auto const& aa = args.loop.alias_for(SI);// If the store always modifies the same mem loc and it is never read, we can hoist itif (!aa.isMustAlias()) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist store: Store does not always write the same memory:" << inst << "\n");return false;}if (aa.isRef()) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist store: Store aliases read memory:" << inst << "\n");return false;}// Writing to memory is side-effectfulreturn can_hoist_sidefx(inst, args);}/// We can only host expressions with side effects if they dominate all exits.static bool can_hoist_sidefx(Instruction const& inst, HoistArgs& args){if (!all_of(args.loop.exits, [&args, &inst](BasicBlock const* exit) {return args.DT.dominates(&inst, exit);})) {LLVM_DEBUG(dbgs() << "UnitLICM: Can't hoist inst w/ sidefx: Does not dominate all exits:" << inst << "\n");return false;}return can_hoist_operands(inst, args);}static bool can_hoist_operands(Instruction const& inst, HoistArgs &args){return std::ranges::all_of(inst.operands(), [&args, &inst](Use const& V) {bool const r = can_hoist(*V.get(), args);LLVM_DEBUG(if (!r) dbgs() << inst << "\n");return r;});} - edit in llvm/lib/Transforms/Scalar/CMakeLists.txt at line 80
UnitLICM.cpp - file addition: UnitLoopInfo.cpp[2.602795540]
#include <cassert>#include <utility>#include <functional>#include <iterator>#include <ranges>#include "llvm/Support/raw_ostream.h"#include "llvm/Support/Debug.h"#include "llvm/Support/Casting.h"#include "llvm/ADT/Statistic.h"#include "llvm/ADT/SmallVector.h"#include "llvm/IR/Value.h"#include "llvm/IR/User.h"#include "llvm/IR/Function.h"#include "llvm/IR/Instruction.h"#include "llvm/IR/Instructions.h"#include "llvm/IR/Dominators.h"#include "llvm/Analysis/AliasAnalysis.h"#include "llvm/Analysis/AliasSetTracker.h"#include "llvm/Analysis/UnitLoopInfo.h"#define DEBUG_TYPE "unit-loop"STATISTIC(NumLoopsDetected, "Number of loops detected");using namespace llvm;using namespace ece479k;// Need to do this conversion because predecessors doesn't satisfy forward-iterator// so we can use it in a filter viewstatic auto preds(BasicBlock const* block){SmallVector<BasicBlock const*> r;auto const p = predecessors(block);r.append(p.begin(), p.end());return r;}#pragma region "UnitLoopAnalysis"/// Main function for running the Loop Identification analysis. This function/// returns information about the loops in the function via the UnitLoopInfo/// objectUnitLoopAnalysis::Result UnitLoopAnalysis::run(Function &F, FunctionAnalysisManager &FAM){auto &DT = FAM.getResult<DominatorTreeAnalysis>(F);auto &AA = FAM.getResult<AAManager>(F);Result Loops;// Need to have this ugliness to support both LLVM 15 and LLVM 16-19#if LLVM_VERSION_MAJOR > 15UnitLoopInfo::alias_results alias = BatchAAResults(AA);#elseUnitLoopInfo::alias_results alias = AA;#endifauto const is_reachable = [&DT](BasicBlock const& bb) {return DT.isReachableFromEntry(&bb);};// Find all back-edges in Ffor (BasicBlock& BB: F | std::views::filter(is_reachable)) {// ReSharper disable once CppDeclarationHidesLocalauto const is_reachable = [&DT](BasicBlock const* bb) {return DT.isReachableFromEntry(bb);};auto const is_backedge = [&DT, &BB](BasicBlock const* bb) {return DT.dominates(&BB, bb);};// We need to filter out unreachble blocks twice because it's possible// for an unreachble block to be the predecessor of a reachable block.// See bad_loop1.ll for canonical case of this.for (BasicBlock const* P: preds(&BB)| std::views::filter(is_reachable)| std::views::filter(is_backedge)){LLVM_DEBUG(dbgs()<< "UnitLoopAnalysis: Found loop in " << F.getName() << " from `"<< BB.getName() << "` to `" << P->getName() << "`");Loops.emplace_back(&BB, const_cast<BasicBlock *>(P), alias, DT);++NumLoopsDetected;}}return Loops;}AnalysisKey UnitLoopAnalysis::Key;#pragma endregion#pragma region "UnitLoopInfo"static BasicBlock* get_preheader(BasicBlock* header, BasicBlock* end){BasicBlock* preheader;// LoopSimplify guarantees 1 preheader and 1 backedge// hence the preds of hedaer are [preheader, end]assert(header->hasNPredecessors(2) && "Loop header preds were not [preheader, backedge]");for (auto* pred : predecessors(header))if (pred != end)preheader = pred;assert(preheader && "Preheader not found");assert(preheader->isLegalToHoistInto() && "Preheader is not hoistable");assert(preheader->getSingleSuccessor() == header && "Preheader is invalid");return preheader;}UnitLoopInfo::UnitLoopInfo(BasicBlock* header, BasicBlock* end, alias_results AA, DominatorTree& DT):header(header), preheader(get_preheader(header, end)), alias_sets(std::make_unique<AliasSetTracker>(AA)){assert(header && "Can't record a loop that starts from a non-existent BasicBlock");assert(end && "Can't record a loop that ends with a non-existent BasicBlock");assert(preheader && "Loop does not have a valid preheader");SmallVector<BasicBlock*, 16> worklist;worklist.emplace_back(end);// Populate loop body by walking up from the end to the headerwhile (!worklist.empty()) {BasicBlock* cur = worklist.back(); worklist.pop_back();// Make sure that we don't add the preds of the headerif (cur == header) {set.insert(header);blocks.emplace_back(header);break;}if (set.insert(cur).second) {blocks.emplace_back(cur);auto preds = predecessors(cur);worklist.append(preds.begin(), preds.end());}}assert(!set.empty() && "Loops must contain at least one BasicBlock");assert(blocks.back() == header && "Loop must start at the header");assert(blocks.front() == end && "Loop must end at the end");for (BasicBlock* block: blocks) {assert(DT.dominates(preheader, block) && "All blocks in the loop must be dominated by the preheader");assert(DT.dominates(header, block) && "All blocks in the loop must be dominated by the header");}// Populate AliasSets for this loopfor (BasicBlock* block: blocks | std::views::reverse)alias_sets->add(*block);// Populate the loop exitsfor (BasicBlock* block : blocks)// if any of the successors of a block point outside the loop, it is an exit from the loopif (any_of(successors(block), [end, this](BasicBlock const* bb) {return !this->contains(bb);}))exits.insert(block);LLVM_DEBUG(dbgs() << " with preheader `" << preheader->getName()<< "`, header `" << header->getName()<< "`, exits [ ";for (BasicBlock const* exit: exits)dbgs() << "`" << exit->getName() << "` ";dbgs() << "]\n";);}bool UnitLoopInfo::contains(BasicBlock const *BB) const{assert(!set.empty() && "Can't check an empty loop");// assert(BB && "Can't look-up a non-existent BasicBlock");return set.contains(BB);}bool UnitLoopInfo::contains(Instruction const *I) const{assert(!set.empty() && "Can't check an empty loop");// assert(I && "Can't look-up a non-existent Instruction");return contains(I->getParent());}bool UnitLoopInfo::contains(Instruction const& I) const{assert(!set.empty() && "Can't check an empty loop");// assert(I && "Can't look-up a non-existent Instruction");return contains(I.getParent());}bool UnitLoopInfo::is_invariant(Value const *V) const{assert(!set.empty() && "Can't be invariant to an empty loop");if (auto const* i = dyn_cast<Instruction>(V))return !contains(i);return true;}bool UnitLoopInfo::is_invariant(Instruction const *I) const{assert(!set.empty() && "Can't be invariant to an empty loop");return !contains(I);}bool UnitLoopInfo::is_invariant(Instruction const& I) const{assert(!set.empty() && "Can't be invariant to an empty loop");return !contains(I);}bool UnitLoopInfo::has_invariant_operands(Instruction const *I) const{assert(!set.empty() && "Can't be invariant to an empty loop");return std::ranges::all_of(I->operands(), [this](Value *v){ return is_invariant(v); });}std::vector<Instruction*> UnitLoopInfo::instructions() const{std::vector<Instruction*> r;for (BasicBlock* block : blocks | std::views::reverse)for (Instruction& inst: *block)r.emplace_back(&inst);return r;}std::vector<Instruction*> UnitLoopInfo::instructions(std::function<bool(Instruction const&)> const& filter) const{std::vector<Instruction*> r;for (BasicBlock* block : blocks | std::views::reverse)for (Instruction& inst: *block | std::views::filter(filter))r.emplace_back(&inst);return r;}AliasSet const& UnitLoopInfo::alias_for(LoadInst const* inst) const{return alias_sets->getAliasSetFor(MemoryLocation::get(inst));}AliasSet const& UnitLoopInfo::alias_for(StoreInst const* inst) const{return alias_sets->getAliasSetFor(MemoryLocation::get(inst));}#pragma endregion - edit in llvm/lib/Analysis/CMakeLists.txt at line 138
UnitLoopInfo.cpp - file addition: UnitLICM.h[2.607628340]
#ifndef INCLUDE_UNIT_LICM_H#define INCLUDE_UNIT_LICM_H#include "llvm/IR/PassManager.h"namespace llvm {class Function;}using namespace llvm;namespace ece479k{/// Loop Invariant Code Motion Optimization Pass////// Implements the relaxed-case:/// - We move pure expressions even if they don't dominate all exits/// - We move expressions with side effects if they dominate all exitsstruct UnitLICM : PassInfoMixin<UnitLICM> {PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);};} // namespace#endif // INCLUDE_UNIT_LICM_H - file addition: UnitLoopInfo.h[2.622017596]
#ifndef INCLUDE_UNIT_LOOP_INFO_H#define INCLUDE_UNIT_LOOP_INFO_H#include <vector>#include <memory>#include "llvm/IR/PassManager.h"#include "llvm/ADT/SmallVector.h"#include "llvm/ADT/SmallPtrSet.h"#include "llvm/Analysis/AliasAnalysis.h"#include "llvm/Analysis/AliasSetTracker.h"namespace llvm {class Value;class Instruction;class BasicBlock;class DominatorTree;class LoadInst;class StoreInst;}using namespace llvm;namespace ece479k {struct UnitLoopInfo {private:// The basic blocks that form this loopSmallPtrSet<BasicBlock*, 16> set;// The blocks in *reverse* orderSmallVector<BasicBlock*, 16> blocks;public:BasicBlock const* header;BasicBlock const* preheader;SmallPtrSet<BasicBlock const*, 4> exits;private:std::unique_ptr<AliasSetTracker> alias_sets;public:using alias_results = AAResults&;// Record a natural loop given its backedge from end to headerUnitLoopInfo(BasicBlock* header, BasicBlock* end, alias_results AA, DominatorTree& DT);/*loop properties*/[[nodiscard]] bool contains(BasicBlock const*) const;[[nodiscard]] bool contains(BasicBlock const&) const;[[nodiscard]] bool contains(Instruction const*) const;[[nodiscard]] bool contains(Instruction const&) const;[[nodiscard]] bool contains(Value const*) const;[[nodiscard]] bool contains(Value const&) const;/*loop invariants*/[[nodiscard]] bool is_invariant(Value const*) const;[[nodiscard]] bool is_invariant(Value const&) const;[[nodiscard]] bool is_invariant(Instruction const*) const;[[nodiscard]] bool is_invariant(Instruction const&) const;[[nodiscard]] bool has_invariant_operands(Instruction const*) const;[[nodiscard]] bool has_invariant_operands(Instruction const&) const;/*utility*/[[nodiscard]] std::vector<Instruction*> instructions() const;[[nodiscard]] std::vector<Instruction*> instructions(std::function<bool(Instruction const&)> const&) const;[[nodiscard]] AliasSet const& alias_for(LoadInst const*) const;[[nodiscard]] AliasSet const& alias_for(StoreInst const*) const;};/// Loop Identification Analysis Pass. Produces a UnitLoopInfo object which/// should contain any information about the loops in the function which is/// needed for your implementation of LICMclass UnitLoopAnalysis : public AnalysisInfoMixin<UnitLoopAnalysis> {friend AnalysisInfoMixin<UnitLoopAnalysis>;static AnalysisKey Key;public:using Result = std::vector<UnitLoopInfo>;Result run(Function &F, FunctionAnalysisManager &AM);};} // namespace ece479k#endif // INCLUDE_UNIT_LOOP_INFO_H