xref: /freebsd-src/contrib/llvm-project/llvm/lib/Analysis/InlineSizeEstimatorAnalysis.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
15ffd83dbSDimitry Andric //===- InlineSizeEstimatorAnalysis.cpp - IR to native size from ML model --===//
25ffd83dbSDimitry Andric //
3349cc55cSDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4349cc55cSDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5349cc55cSDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
65ffd83dbSDimitry Andric //
75ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
85ffd83dbSDimitry Andric //
95ffd83dbSDimitry Andric // This implements feature and label extraction for offline supervised learning
105ffd83dbSDimitry Andric // of a IR to native size model.
115ffd83dbSDimitry Andric //
125ffd83dbSDimitry Andric //===----------------------------------------------------------------------===//
135ffd83dbSDimitry Andric #include "llvm/Analysis/InlineSizeEstimatorAnalysis.h"
145ffd83dbSDimitry Andric 
15*bdd1243dSDimitry Andric #ifdef LLVM_HAVE_TFLITE
165ffd83dbSDimitry Andric #include "llvm/Analysis/Utils/TFUtils.h"
175ffd83dbSDimitry Andric #endif
185ffd83dbSDimitry Andric #include "llvm/IR/Function.h"
195ffd83dbSDimitry Andric #include "llvm/IR/PassManager.h"
205ffd83dbSDimitry Andric #include "llvm/Support/raw_ostream.h"
215ffd83dbSDimitry Andric 
225ffd83dbSDimitry Andric using namespace llvm;
235ffd83dbSDimitry Andric 
245ffd83dbSDimitry Andric AnalysisKey InlineSizeEstimatorAnalysis::Key;
255ffd83dbSDimitry Andric 
26*bdd1243dSDimitry Andric #ifdef LLVM_HAVE_TFLITE
2781ad6265SDimitry Andric #include "llvm/Analysis/LoopInfo.h"
2881ad6265SDimitry Andric #include "llvm/Analysis/TargetLibraryInfo.h"
2981ad6265SDimitry Andric #include "llvm/Analysis/TargetTransformInfo.h"
3081ad6265SDimitry Andric #include "llvm/IR/BasicBlock.h"
3181ad6265SDimitry Andric #include "llvm/IR/Dominators.h"
3281ad6265SDimitry Andric #include "llvm/IR/Instructions.h"
3381ad6265SDimitry Andric #include "llvm/Support/Casting.h"
3481ad6265SDimitry Andric #include "llvm/Support/CommandLine.h"
3581ad6265SDimitry Andric #include <algorithm>
3681ad6265SDimitry Andric #include <deque>
37*bdd1243dSDimitry Andric #include <optional>
3881ad6265SDimitry Andric 
395ffd83dbSDimitry Andric cl::opt<std::string> TFIR2NativeModelPath(
405ffd83dbSDimitry Andric     "ml-inliner-ir2native-model", cl::Hidden,
415ffd83dbSDimitry Andric     cl::desc("Path to saved model evaluating native size from IR."));
425ffd83dbSDimitry Andric 
4381ad6265SDimitry Andric #define DEBUG_TYPE "inline-size-estimator"
445ffd83dbSDimitry Andric namespace {
455ffd83dbSDimitry Andric unsigned getMaxInstructionID() {
465ffd83dbSDimitry Andric #define LAST_OTHER_INST(NR) return NR;
475ffd83dbSDimitry Andric #include "llvm/IR/Instruction.def"
485ffd83dbSDimitry Andric }
495ffd83dbSDimitry Andric 
505ffd83dbSDimitry Andric class IRToNativeSizeLearning {
515ffd83dbSDimitry Andric public:
525ffd83dbSDimitry Andric   enum class NamedFeatureIndex : size_t {
535ffd83dbSDimitry Andric     InitialSize,
545ffd83dbSDimitry Andric     Blocks,
555ffd83dbSDimitry Andric     Calls,
565ffd83dbSDimitry Andric     IsLocal,
575ffd83dbSDimitry Andric     IsLinkOnceODR,
585ffd83dbSDimitry Andric     IsLinkOnce,
595ffd83dbSDimitry Andric     Loops,
605ffd83dbSDimitry Andric     MaxLoopDepth,
615ffd83dbSDimitry Andric     MaxDomTreeLevel,
625ffd83dbSDimitry Andric 
635ffd83dbSDimitry Andric     NumNamedFeatures
645ffd83dbSDimitry Andric   };
655ffd83dbSDimitry Andric   static const size_t NumNamedFeatures =
665ffd83dbSDimitry Andric       static_cast<size_t>(NamedFeatureIndex::NumNamedFeatures);
675ffd83dbSDimitry Andric   struct FunctionFeatures {
685ffd83dbSDimitry Andric     static const size_t FeatureCount;
695ffd83dbSDimitry Andric 
705ffd83dbSDimitry Andric     std::array<int32_t, NumNamedFeatures> NamedFeatures = {0};
715ffd83dbSDimitry Andric     std::vector<int32_t> InstructionHistogram;
725ffd83dbSDimitry Andric     std::vector<int32_t> InstructionPairHistogram;
735ffd83dbSDimitry Andric 
745ffd83dbSDimitry Andric     void fillTensor(int32_t *Ptr) const;
755ffd83dbSDimitry Andric     int32_t &operator[](NamedFeatureIndex Pos) {
765ffd83dbSDimitry Andric       return NamedFeatures[static_cast<size_t>(Pos)];
775ffd83dbSDimitry Andric     }
785ffd83dbSDimitry Andric   };
795ffd83dbSDimitry Andric   IRToNativeSizeLearning() = default;
805ffd83dbSDimitry Andric 
815ffd83dbSDimitry Andric   static FunctionFeatures getFunctionFeatures(Function &F,
825ffd83dbSDimitry Andric                                               FunctionAnalysisManager &FAM);
835ffd83dbSDimitry Andric };
845ffd83dbSDimitry Andric 
855ffd83dbSDimitry Andric // This is a point in time - we determined including these pairs of
865ffd83dbSDimitry Andric // consecutive instructions (in the IR layout available at inline time) as
875ffd83dbSDimitry Andric // features improves the model performance. We want to move away from manual
885ffd83dbSDimitry Andric // feature selection.
89e8d8bef9SDimitry Andric // The array is given in opcode pairs rather than labels because 1) labels
90e8d8bef9SDimitry Andric // weren't readily available, and 2) the successions were hand - extracted.
91e8d8bef9SDimitry Andric //
92e8d8bef9SDimitry Andric // This array must be sorted.
93e8d8bef9SDimitry Andric static const std::array<std::pair<size_t, size_t>, 137>
94e8d8bef9SDimitry Andric     ImportantInstructionSuccessions{
95e8d8bef9SDimitry Andric         {{1, 1},   {1, 4},   {1, 5},   {1, 7},   {1, 8},   {1, 9},   {1, 11},
96e8d8bef9SDimitry Andric          {1, 12},  {1, 13},  {1, 14},  {1, 18},  {1, 20},  {1, 22},  {1, 24},
97e8d8bef9SDimitry Andric          {1, 25},  {1, 26},  {1, 27},  {1, 28},  {1, 29},  {1, 30},  {1, 31},
98e8d8bef9SDimitry Andric          {1, 32},  {1, 33},  {1, 34},  {1, 39},  {1, 40},  {1, 42},  {1, 45},
99e8d8bef9SDimitry Andric          {2, 1},   {2, 2},   {2, 13},  {2, 28},  {2, 29},  {2, 32},  {2, 33},
100e8d8bef9SDimitry Andric          {2, 34},  {2, 38},  {2, 48},  {2, 49},  {2, 53},  {2, 55},  {2, 56},
101e8d8bef9SDimitry Andric          {13, 2},  {13, 13}, {13, 26}, {13, 33}, {13, 34}, {13, 56}, {15, 27},
102e8d8bef9SDimitry Andric          {28, 2},  {28, 48}, {28, 53}, {29, 2},  {29, 33}, {29, 56}, {31, 31},
103e8d8bef9SDimitry Andric          {31, 33}, {31, 34}, {31, 49}, {32, 1},  {32, 2},  {32, 13}, {32, 15},
104e8d8bef9SDimitry Andric          {32, 28}, {32, 29}, {32, 32}, {32, 33}, {32, 34}, {32, 39}, {32, 40},
105e8d8bef9SDimitry Andric          {32, 48}, {32, 49}, {32, 53}, {32, 56}, {33, 1},  {33, 2},  {33, 32},
106e8d8bef9SDimitry Andric          {33, 33}, {33, 34}, {33, 49}, {33, 53}, {33, 56}, {34, 1},  {34, 2},
107e8d8bef9SDimitry Andric          {34, 32}, {34, 33}, {34, 34}, {34, 49}, {34, 53}, {34, 56}, {38, 34},
108e8d8bef9SDimitry Andric          {39, 57}, {40, 34}, {47, 15}, {47, 49}, {48, 2},  {48, 34}, {48, 56},
109e8d8bef9SDimitry Andric          {49, 1},  {49, 2},  {49, 28}, {49, 32}, {49, 33}, {49, 34}, {49, 39},
110e8d8bef9SDimitry Andric          {49, 49}, {49, 56}, {53, 1},  {53, 2},  {53, 28}, {53, 34}, {53, 53},
111e8d8bef9SDimitry Andric          {53, 57}, {55, 1},  {55, 28}, {55, 34}, {55, 53}, {55, 55}, {55, 56},
112e8d8bef9SDimitry Andric          {56, 1},  {56, 2},  {56, 7},  {56, 13}, {56, 32}, {56, 33}, {56, 34},
113e8d8bef9SDimitry Andric          {56, 49}, {56, 53}, {56, 56}, {56, 64}, {57, 34}, {57, 56}, {57, 57},
114e8d8bef9SDimitry Andric          {64, 1},  {64, 64}, {65, 1},  {65, 65}}};
1155ffd83dbSDimitry Andric 
1165ffd83dbSDimitry Andric // We have: 9 calculated features (the features here); 1 feature for each
1175ffd83dbSDimitry Andric // instruction opcode; and 1 feature for each manually-identified sequence.
1185ffd83dbSDimitry Andric // For the latter 2, we build a histogram: we count the number of
1195ffd83dbSDimitry Andric // occurrences of each instruction opcode or succession of instructions,
1205ffd83dbSDimitry Andric // respectively.
1215ffd83dbSDimitry Andric // Note that instruction opcodes start from 1. For convenience, we also have an
1225ffd83dbSDimitry Andric // always 0 feature for the '0' opcode, hence the extra 1.
1235ffd83dbSDimitry Andric const size_t IRToNativeSizeLearning::FunctionFeatures::FeatureCount =
124e8d8bef9SDimitry Andric     ImportantInstructionSuccessions.size() + getMaxInstructionID() + 1 +
125e8d8bef9SDimitry Andric     IRToNativeSizeLearning::NumNamedFeatures;
1265ffd83dbSDimitry Andric 
1275ffd83dbSDimitry Andric size_t getSize(Function &F, TargetTransformInfo &TTI) {
1285ffd83dbSDimitry Andric   size_t Ret = 0;
129e8d8bef9SDimitry Andric   for (const auto &BB : F)
130e8d8bef9SDimitry Andric     for (const auto &I : BB)
131e8d8bef9SDimitry Andric       Ret += *(TTI.getInstructionCost(
132e8d8bef9SDimitry Andric           &I, TargetTransformInfo::TargetCostKind::TCK_CodeSize).getValue());
1335ffd83dbSDimitry Andric   return Ret;
1345ffd83dbSDimitry Andric }
1355ffd83dbSDimitry Andric 
1365ffd83dbSDimitry Andric size_t getSize(Function &F, FunctionAnalysisManager &FAM) {
1375ffd83dbSDimitry Andric   auto &TTI = FAM.getResult<TargetIRAnalysis>(F);
1385ffd83dbSDimitry Andric   return getSize(F, TTI);
1395ffd83dbSDimitry Andric }
1405ffd83dbSDimitry Andric 
1415ffd83dbSDimitry Andric unsigned getMaxDominatorTreeDepth(const Function &F,
1425ffd83dbSDimitry Andric                                   const DominatorTree &Tree) {
1435ffd83dbSDimitry Andric   unsigned Ret = 0;
144e8d8bef9SDimitry Andric   for (const auto &BB : F)
145e8d8bef9SDimitry Andric     if (const auto *TN = Tree.getNode(&BB))
1465ffd83dbSDimitry Andric       Ret = std::max(Ret, TN->getLevel());
1475ffd83dbSDimitry Andric   return Ret;
1485ffd83dbSDimitry Andric }
1495ffd83dbSDimitry Andric } // namespace
1505ffd83dbSDimitry Andric 
1515ffd83dbSDimitry Andric IRToNativeSizeLearning::FunctionFeatures
1525ffd83dbSDimitry Andric IRToNativeSizeLearning::getFunctionFeatures(Function &F,
1535ffd83dbSDimitry Andric                                             FunctionAnalysisManager &FAM) {
154e8d8bef9SDimitry Andric   assert(llvm::is_sorted(ImportantInstructionSuccessions) &&
155e8d8bef9SDimitry Andric          "expected function features are sorted");
1565ffd83dbSDimitry Andric 
1575ffd83dbSDimitry Andric   auto &DomTree = FAM.getResult<DominatorTreeAnalysis>(F);
1585ffd83dbSDimitry Andric   FunctionFeatures FF;
1595ffd83dbSDimitry Andric   size_t InstrCount = getMaxInstructionID() + 1;
1605ffd83dbSDimitry Andric   FF.InstructionHistogram.resize(InstrCount);
1615ffd83dbSDimitry Andric 
162e8d8bef9SDimitry Andric   FF.InstructionPairHistogram.resize(ImportantInstructionSuccessions.size());
1635ffd83dbSDimitry Andric 
164e8d8bef9SDimitry Andric   int StartID = 0;
165e8d8bef9SDimitry Andric   int LastID = StartID;
1665ffd83dbSDimitry Andric   auto getPairIndex = [](size_t a, size_t b) {
167e8d8bef9SDimitry Andric     auto I = llvm::find(ImportantInstructionSuccessions, std::make_pair(a, b));
168e8d8bef9SDimitry Andric     if (I == ImportantInstructionSuccessions.end())
1695ffd83dbSDimitry Andric       return -1;
170e8d8bef9SDimitry Andric     return static_cast<int>(
171e8d8bef9SDimitry Andric         std::distance(ImportantInstructionSuccessions.begin(), I));
1725ffd83dbSDimitry Andric   };
1735ffd83dbSDimitry Andric 
1745ffd83dbSDimitry Andric   // We don't want debug calls, because they'd just add noise.
175e8d8bef9SDimitry Andric   for (const auto &BB : F) {
176e8d8bef9SDimitry Andric     for (const auto &I : BB.instructionsWithoutDebug()) {
177e8d8bef9SDimitry Andric       auto ID = I.getOpcode();
1785ffd83dbSDimitry Andric 
1795ffd83dbSDimitry Andric       ++FF.InstructionHistogram[ID];
1805ffd83dbSDimitry Andric       int PairIndex = getPairIndex(LastID, ID);
1815ffd83dbSDimitry Andric       if (PairIndex >= 0)
1825ffd83dbSDimitry Andric         ++FF.InstructionPairHistogram[PairIndex];
1835ffd83dbSDimitry Andric       LastID = ID;
184e8d8bef9SDimitry Andric       if (isa<CallBase>(I))
1855ffd83dbSDimitry Andric         ++FF[NamedFeatureIndex::Calls];
1865ffd83dbSDimitry Andric     }
1875ffd83dbSDimitry Andric   }
1885ffd83dbSDimitry Andric 
1895ffd83dbSDimitry Andric   FF[NamedFeatureIndex::InitialSize] = getSize(F, FAM);
1905ffd83dbSDimitry Andric   FF[NamedFeatureIndex::IsLocal] = F.hasLocalLinkage();
1915ffd83dbSDimitry Andric   FF[NamedFeatureIndex::IsLinkOnceODR] = F.hasLinkOnceODRLinkage();
1925ffd83dbSDimitry Andric   FF[NamedFeatureIndex::IsLinkOnce] = F.hasLinkOnceLinkage();
193*bdd1243dSDimitry Andric   FF[NamedFeatureIndex::Blocks] = F.size();
1945ffd83dbSDimitry Andric   auto &LI = FAM.getResult<LoopAnalysis>(F);
1955ffd83dbSDimitry Andric   FF[NamedFeatureIndex::Loops] = std::distance(LI.begin(), LI.end());
1965ffd83dbSDimitry Andric   for (auto &L : LI)
1975ffd83dbSDimitry Andric     FF[NamedFeatureIndex::MaxLoopDepth] =
1985ffd83dbSDimitry Andric         std::max(FF[NamedFeatureIndex::MaxLoopDepth],
1995ffd83dbSDimitry Andric                  static_cast<int32_t>(L->getLoopDepth()));
2005ffd83dbSDimitry Andric   FF[NamedFeatureIndex::MaxDomTreeLevel] = getMaxDominatorTreeDepth(F, DomTree);
2015ffd83dbSDimitry Andric   return FF;
2025ffd83dbSDimitry Andric }
2035ffd83dbSDimitry Andric 
2045ffd83dbSDimitry Andric void IRToNativeSizeLearning::FunctionFeatures::fillTensor(int32_t *Ptr) const {
2055ffd83dbSDimitry Andric   std::copy(NamedFeatures.begin(), NamedFeatures.end(), Ptr);
2065ffd83dbSDimitry Andric   Ptr += NamedFeatures.size();
2075ffd83dbSDimitry Andric   std::copy(InstructionHistogram.begin(), InstructionHistogram.end(), Ptr);
2085ffd83dbSDimitry Andric   Ptr += InstructionHistogram.size();
2095ffd83dbSDimitry Andric   std::copy(InstructionPairHistogram.begin(), InstructionPairHistogram.end(),
2105ffd83dbSDimitry Andric             Ptr);
2115ffd83dbSDimitry Andric }
2125ffd83dbSDimitry Andric 
2135ffd83dbSDimitry Andric bool InlineSizeEstimatorAnalysis::isEvaluatorRequested() {
2145ffd83dbSDimitry Andric   return !TFIR2NativeModelPath.empty();
2155ffd83dbSDimitry Andric }
2165ffd83dbSDimitry Andric 
2175ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::InlineSizeEstimatorAnalysis() {
2185ffd83dbSDimitry Andric   if (!isEvaluatorRequested()) {
2195ffd83dbSDimitry Andric     return;
2205ffd83dbSDimitry Andric   }
221e8d8bef9SDimitry Andric   std::vector<TensorSpec> InputSpecs{TensorSpec::createSpec<int32_t>(
222e8d8bef9SDimitry Andric       "serving_default_input_1",
223e8d8bef9SDimitry Andric       {1, static_cast<int64_t>(
224e8d8bef9SDimitry Andric               IRToNativeSizeLearning::FunctionFeatures::FeatureCount)})};
225e8d8bef9SDimitry Andric   std::vector<TensorSpec> OutputSpecs{
226e8d8bef9SDimitry Andric       TensorSpec::createSpec<float>("StatefulPartitionedCall", {1})};
2275ffd83dbSDimitry Andric   Evaluator = std::make_unique<TFModelEvaluator>(
228e8d8bef9SDimitry Andric       TFIR2NativeModelPath.getValue().c_str(), InputSpecs, OutputSpecs);
2295ffd83dbSDimitry Andric   if (!Evaluator || !Evaluator->isValid()) {
2305ffd83dbSDimitry Andric     Evaluator.reset();
2315ffd83dbSDimitry Andric     return;
2325ffd83dbSDimitry Andric   }
2335ffd83dbSDimitry Andric }
2345ffd83dbSDimitry Andric 
2355ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::Result
2365ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::run(const Function &F,
2375ffd83dbSDimitry Andric                                  FunctionAnalysisManager &FAM) {
2385ffd83dbSDimitry Andric   if (!Evaluator)
239*bdd1243dSDimitry Andric     return std::nullopt;
2405ffd83dbSDimitry Andric   auto Features = IRToNativeSizeLearning::getFunctionFeatures(
2415ffd83dbSDimitry Andric       const_cast<Function &>(F), FAM);
2425ffd83dbSDimitry Andric   int32_t *V = Evaluator->getInput<int32_t>(0);
2435ffd83dbSDimitry Andric   Features.fillTensor(V);
2445ffd83dbSDimitry Andric   auto ER = Evaluator->evaluate();
2455ffd83dbSDimitry Andric   if (!ER)
246*bdd1243dSDimitry Andric     return std::nullopt;
2475ffd83dbSDimitry Andric   float Ret = *ER->getTensorValue<float>(0);
2485ffd83dbSDimitry Andric   if (Ret < 0.0)
2495ffd83dbSDimitry Andric     Ret = 0.0;
2505ffd83dbSDimitry Andric   return static_cast<size_t>(Ret);
2515ffd83dbSDimitry Andric }
2525ffd83dbSDimitry Andric 
2535ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::~InlineSizeEstimatorAnalysis() {}
2545ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::InlineSizeEstimatorAnalysis(
2555ffd83dbSDimitry Andric     InlineSizeEstimatorAnalysis &&Other)
2565ffd83dbSDimitry Andric     : Evaluator(std::move(Other.Evaluator)) {}
2575ffd83dbSDimitry Andric 
2585ffd83dbSDimitry Andric #else
2595ffd83dbSDimitry Andric namespace llvm {
2605ffd83dbSDimitry Andric class TFModelEvaluator {};
2615ffd83dbSDimitry Andric } // namespace llvm
26281ad6265SDimitry Andric InlineSizeEstimatorAnalysis::InlineSizeEstimatorAnalysis() = default;
2635ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis ::InlineSizeEstimatorAnalysis(
2645ffd83dbSDimitry Andric     InlineSizeEstimatorAnalysis &&) {}
26581ad6265SDimitry Andric InlineSizeEstimatorAnalysis::~InlineSizeEstimatorAnalysis() = default;
2665ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::Result
2675ffd83dbSDimitry Andric InlineSizeEstimatorAnalysis::run(const Function &F,
2685ffd83dbSDimitry Andric                                  FunctionAnalysisManager &FAM) {
269*bdd1243dSDimitry Andric   return std::nullopt;
2705ffd83dbSDimitry Andric }
2715ffd83dbSDimitry Andric bool InlineSizeEstimatorAnalysis::isEvaluatorRequested() { return false; }
2725ffd83dbSDimitry Andric #endif
273e8d8bef9SDimitry Andric 
274e8d8bef9SDimitry Andric PreservedAnalyses
275e8d8bef9SDimitry Andric InlineSizeEstimatorAnalysisPrinterPass::run(Function &F,
276e8d8bef9SDimitry Andric                                             FunctionAnalysisManager &AM) {
277e8d8bef9SDimitry Andric   OS << "[InlineSizeEstimatorAnalysis] size estimate for " << F.getName()
278e8d8bef9SDimitry Andric      << ": " << AM.getResult<InlineSizeEstimatorAnalysis>(F) << "\n";
279e8d8bef9SDimitry Andric   return PreservedAnalyses::all();
280e8d8bef9SDimitry Andric }
281