From 9773a0bfd29580c31867afccada947457617628e Mon Sep 17 00:00:00 2001
From: tsczajkowski <tsczajkowski@gmail.com>
Date: Thu, 22 Aug 2024 23:56:11 -0400
Subject: [PATCH] [ACPO] ACPO Infrastructure

This change introduces ACPO ML infrastructure to enable use of ML models
within LLVM compiler using a simple interface.
---
 ACPO_README.md                                |   36 +
 llvm/CMakeLists.txt                           |    4 +
 .../llvm/Analysis/ACPOCollectFeatures.h       |  296 ++++
 llvm/include/llvm/Analysis/ACPOMLInterface.h  |  482 ++++++
 llvm/include/llvm/Analysis/ACPOModel.h        |  122 ++
 llvm/include/llvm/Analysis/ACPOModelRunner.h  |   39 +
 llvm/include/llvm/Analysis/AOTModelRunner.h   |  203 +++
 llvm/include/llvm/Analysis/CallHeight.h       |   72 +
 llvm/include/llvm/Analysis/DumpCallsite.h     |   27 +
 llvm/include/llvm/Analysis/DumpFeature.h      |  194 +++
 llvm/include/llvm/Analysis/LoopInfo.h         |   11 +
 .../llvm/Analysis/ModelDataCollector.h        |  108 ++
 llvm/include/llvm/InitializePasses.h          |    8 +
 llvm/lib/Analysis/ACPOCollectFeatures.cpp     | 1258 +++++++++++++++
 llvm/lib/Analysis/ACPOMLInterface.cpp         | 1405 +++++++++++++++++
 llvm/lib/Analysis/ACPOModel.cpp               |   63 +
 llvm/lib/Analysis/CMakeLists.txt              |   32 +
 llvm/lib/Analysis/CallHeight.cpp              |   89 ++
 llvm/lib/Analysis/DumpCallsite.cpp            |   82 +
 llvm/lib/Analysis/DumpFeature.cpp             |  575 +++++++
 llvm/lib/Analysis/ModelDataCollector.cpp      |  350 ++++
 llvm/lib/CodeGen/CMakeLists.txt               |    2 +-
 llvm/lib/IR/AsmWriter.cpp                     |  220 ++-
 llvm/lib/Passes/PassBuilder.cpp               |    6 +
 llvm/lib/Passes/PassBuilderPipelines.cpp      |   14 +
 llvm/lib/Passes/PassRegistry.def              |   10 +
 26 files changed, 5679 insertions(+), 29 deletions(-)
 create mode 100644 ACPO_README.md
 create mode 100644 llvm/include/llvm/Analysis/ACPOCollectFeatures.h
 create mode 100644 llvm/include/llvm/Analysis/ACPOMLInterface.h
 create mode 100644 llvm/include/llvm/Analysis/ACPOModel.h
 create mode 100644 llvm/include/llvm/Analysis/ACPOModelRunner.h
 create mode 100644 llvm/include/llvm/Analysis/AOTModelRunner.h
 create mode 100644 llvm/include/llvm/Analysis/CallHeight.h
 create mode 100644 llvm/include/llvm/Analysis/DumpCallsite.h
 create mode 100644 llvm/include/llvm/Analysis/DumpFeature.h
 create mode 100644 llvm/include/llvm/Analysis/ModelDataCollector.h
 create mode 100644 llvm/lib/Analysis/ACPOCollectFeatures.cpp
 create mode 100644 llvm/lib/Analysis/ACPOMLInterface.cpp
 create mode 100644 llvm/lib/Analysis/ACPOModel.cpp
 create mode 100644 llvm/lib/Analysis/CallHeight.cpp
 create mode 100644 llvm/lib/Analysis/DumpCallsite.cpp
 create mode 100644 llvm/lib/Analysis/DumpFeature.cpp
 create mode 100644 llvm/lib/Analysis/ModelDataCollector.cpp

diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 79de9eb2e3e7..b0afb47a7243 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -1001,6 +1001,9 @@ endif()
 #
 set(TENSORFLOW_AOT_PATH "" CACHE PATH "Path to TensorFlow pip install dir")
 
+set(LLVM_INLINER_MODEL_PATH "" CACHE PATH "Path to the inliner model")
+set(ACPO_AOT OFF CACHE BOOL "Whether or not ACPO AOT is enabled")
+
 if (NOT TENSORFLOW_AOT_PATH STREQUAL "")
   set(LLVM_HAVE_TF_AOT "ON" CACHE BOOL "Tensorflow AOT available")
   set(TENSORFLOW_AOT_COMPILER
@@ -1009,6 +1012,7 @@ if (NOT TENSORFLOW_AOT_PATH STREQUAL "")
   include_directories(${TENSORFLOW_AOT_PATH}/include)
   add_subdirectory(${TENSORFLOW_AOT_PATH}/xla_aot_runtime_src
     ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/tf_runtime)
+  target_compile_definitions(tf_xla_runtime_objects PUBLIC EIGEN_NEON_GEBP_NR=4) # Fix for issue https://github.com/tensorflow/tensorflow/issues/58481
   install(TARGETS tf_xla_runtime EXPORT LLVMExports
     ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT tf_xla_runtime)
   set_property(GLOBAL APPEND PROPERTY LLVM_EXPORTS tf_xla_runtime)
diff --git a/llvm/include/llvm/Analysis/ACPOCollectFeatures.h b/llvm/include/llvm/Analysis/ACPOCollectFeatures.h
new file mode 100644
index 000000000000..ec62b559542d
--- /dev/null
+++ b/llvm/include/llvm/Analysis/ACPOCollectFeatures.h
@@ -0,0 +1,296 @@
+//===- ACPOCollectFeatures.h - ACPO Class for Feature Collection ----------===//
+//
+// 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 header file defines the type, scope, and number of features to be
+// collected on a given ACPOModel class from all available features.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_ANALYSIS_ACPOCOLLECTFEATURES_H
+#define LLVM_ANALYSIS_ACPOCOLLECTFEATURES_H
+#include "llvm/Analysis/InlineAdvisor.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/PassManager.h"
+
+#include <ios>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace llvm {
+class ACPOCollectFeatures {
+public:
+  // A feature is related to one of the following scope
+  enum class Scope {
+    Module,
+    Function,
+    Loop,
+    Callgraph,
+    CallSite,
+    NumOfScope,
+  };
+
+  // In the future as more features are added, features can be calculated
+  // simultanously.
+  // Suppose feature A and B could be calculated in the same loop,
+  // then it would make sense to calculate both the features at the same time
+  // and save it in a cache system
+  // (which could be implemented similarly like Dumpfeatures.h/cpp).
+  enum class GroupID {
+    EdgeNodeCount,
+    FPIRelated,
+    HotColdCallSite,
+    InlineCostFeatureGroup,
+    ACPOFIExtendedFeatures,
+    NumOfGroupID
+  };
+
+  // List of features we support to be calculated.
+  // (1) For each feature there should be a corresponding scope on which it
+  // depends on for calculating.
+  // (2) A feature may belong in a group for which those features could be
+  //     calculated together.
+  // (3) Once you decided to add a feature you should register it to all the
+  //     static maps in the .cpp file. Except for some special indicator enum's
+  //     like InlineCostFeatureGroupBegin/End
+  enum class FeatureIndex {
+    // Begin: InlineCostFeatureGroup
+    InlineCostFeatureGroupBegin,
+    SROASavings,
+    SROALosses,
+    LoadElimination,
+    CallPenalty,
+    CallArgumentSetup,
+    LoadRelativeIntrinsic,
+    LoweredCallArgSetup,
+    IndirectCallPenalty,
+    JumpTablePenalty,
+    CaseClusterPenalty,
+    SwitchPenalty,
+    UnsimplifiedCommonInstructions,
+    NumLoops,
+    DeadBlocks,
+    SimplifiedInstructions,
+    ConstantArgs,
+    ConstantOffsetPtrArgs,
+    CallSiteCost,
+    ColdCcPenalty,
+    LastCallToStaticBonus,
+    IsMultipleBlocks,
+    NestedInlines,
+    NestedInlineCostEstimate,
+    Threshold,
+    InlineCostFeatureGroupEnd,
+    // End: InlineCostFeatureGroup
+
+    // Begin: FPIRelated
+    BasicBlockCount,
+    BlocksReachedFromConditionalInstruction,
+    Uses,
+    // End: FPIRelated
+
+    // Begin: EdgeNodeCount
+    EdgeCount,
+    NodeCount,
+    // End: EdgeNodeCount
+
+    // Begin: HotColdCallsite
+    ColdCallSite,
+    HotCallSite,
+    // End: HotColdCallsite
+
+    // Begin: ACPOFIExtendedFeatures
+    ACPOFIExtendedFeaturesNamedFeatureBegin,
+    ACPOFIExtendedFeaturesInitialSize,
+    ACPOFIExtendedFeaturesBlocks,
+    ACPOFIExtendedFeaturesCalls,
+    ACPOFIExtendedFeaturesIsLocal,
+    ACPOFIExtendedFeaturesIsLinkOnceODR,
+    ACPOFIExtendedFeaturesIsLinkOnce,
+    ACPOFIExtendedFeaturesLoops,
+    ACPOFIExtendedFeaturesMaxLoopDepth,
+    ACPOFIExtendedFeaturesMaxDomTreeLevel,
+    ACPOFIExtendedFeaturesPtrArgs,
+    ACPOFIExtendedFeaturesPtrCallee,
+    ACPOFIExtendedFeaturesCallReturnPtr,
+    ACPOFIExtendedFeaturesConditionalBranch,
+    ACPOFIExtendedFeaturesCBwithArg,
+    ACPOFIExtendedFeaturesCallerHeight,
+    ACPOFIExtendedFeaturesCallUsage,
+    ACPOFIExtendedFeaturesIsRecursive,
+    ACPOFIExtendedFeaturesNumCallsiteInLoop,
+    ACPOFIExtendedFeaturesNumOfCallUsesInLoop,
+    ACPOFIExtendedFeaturesEntryBlockFreq,
+    ACPOFIExtendedFeaturesMaxCallsiteBlockFreq,
+    ACPOFIExtendedFeaturesNamedFeatureEnd,
+    ACPOFIExtendedFeaturesFloatFeatureBegin,
+    ACPOFIExtendedFeaturesInstructionPerBlock,
+    ACPOFIExtendedFeaturesSuccessorPerBlock,
+    ACPOFIExtendedFeaturesAvgVecInstr,
+    ACPOFIExtendedFeaturesAvgNestedLoopLevel,
+    ACPOFIExtendedFeaturesInstrPerLoop,
+    ACPOFIExtendedFeaturesBlockWithMultipleSuccecorsPerLoop,
+    ACPOFIExtendedFeaturesFloatFeatureEnd,
+    // End: ACPOFIExtendedFeatures
+
+    CallerBlockFreq,
+    CallSiteHeight,
+    ConstantParam,
+    CostEstimate,
+    LoopLevel,
+    MandatoryKind,
+    MandatoryOnly,
+    OptCode,
+    IsIndirectCall,
+    IsInInnerLoop,
+    IsMustTailCall,
+    IsTailCall,
+    NumOfFeatures
+  };
+
+  struct AnalysisManagers {
+    FunctionAnalysisManager *FAM = nullptr;
+    ModuleAnalysisManager *MAM = nullptr;
+  };
+
+  // ScopeInfo is a struct that contains the correpsonding needed information to
+  // calculate the corresponding feature.
+  struct ScopeInfo {
+    Function *F = nullptr;
+    CallBase *CB = nullptr;
+    BasicBlock *BB = nullptr;
+    Module *M = nullptr;
+    Loop *L = nullptr;
+    // Can add Instructions or other types later.
+  };
+
+  struct OtherInfo {
+    bool MandatoryOnly = false;
+    InlineAdvisor *IA = nullptr;
+  };
+
+  // FeatureInfo should contain all the relevant information to calculate
+  // the corresponding FeatureIndex.
+  struct FeatureInfo {
+    // When Idx = NumOfFeatures. We assume this is a global FeatureInfo.
+    FeatureIndex Idx;
+    // Once we have the Idx we should know the following two attribute.
+    // Scope ScopeIdx //
+    // GroupID Group //
+    AnalysisManagers Managers;
+    ScopeInfo SI;
+    OtherInfo OI;
+  };
+
+  using FeatureValueMap = std::unordered_map<FeatureIndex, std::string>;
+  using FeatureInfoMap = std::unordered_map<FeatureIndex, FeatureInfo>;
+  using FeaturesInfo = std::vector<FeatureInfo>;
+  using Scopes = std::vector<Scope>;
+  using GroupIDs = std::vector<GroupID>;
+  typedef void (*CalculateFeatureFunction)(ACPOCollectFeatures &,
+                                           const FeatureInfo &);
+
+  // Constructors/Destructors
+  ACPOCollectFeatures();
+  ACPOCollectFeatures(FeatureInfo GlobalInfo);
+  ~ACPOCollectFeatures();
+
+  // Setters/getters
+  void setFeatureValue(FeatureIndex Idx, std::string Val);
+
+  void setFeatureInfo(FeatureIndex Idx, FeatureInfo Info);
+
+  void setFeatureValueAndInfo(FeatureIndex Idx, FeatureInfo Info,
+                              std::string Val);
+
+  void setGlobalFeatureInfo(FeatureInfo &Info);
+
+  std::string getFeature(FeatureIndex Idx) const;
+
+  // Check if the feature is alrady calculated.
+  bool containsFeature(FeatureIndex);
+  bool containsFeature(GroupID);
+
+  static std::string getFeatureName(FeatureIndex Idx);
+  static GroupID getFeatureGroup(FeatureIndex Idx);
+  static Scope getFeatureScope(FeatureIndex Idx);
+  static std::set<FeatureIndex> getGroupFeatures(GroupID Group);
+  static std::set<FeatureIndex> getScopeFeatures(Scope S);
+
+  void clearFeatureValueMap();
+  bool registeredFeature(FeatureIndex Idx) const;
+
+  // Calculate and Return the feature values specified by FeaturesInfo
+  FeatureValueMap getFeaturesPair(FeaturesInfo Features);
+
+  // Calculate and Return the feature values specified from [Beg, End)
+  // TODO: Make a similar method for Scopes and GroupIDs
+  FeatureValueMap getFeaturesPair(FeatureIndex Beg, FeatureIndex End);
+
+  // Calculate and Return the feature values specified by Scope.
+  FeatureValueMap getFeaturesPair(Scopes);
+
+  // Calculate and Return the feature values specified by GroupID.
+  FeatureValueMap getFeaturesPair(GroupIDs);
+
+  static InlineAdvisor::MandatoryInliningKind
+  getMandatoryKind(CallBase &CB, FunctionAnalysisManager &FAM,
+                   OptimizationRemarkEmitter &ORE);
+
+  static void clearFunctionLevel();
+  static void insertFunctionLevel(const Function *, unsigned);
+  static std::optional<unsigned> getFunctionLevel(const Function *);
+
+private:
+  // Global mappings.
+  // FeatureIndexToName and FeatureIndexToScope should be a one to one mapping.
+  static const std::unordered_map<FeatureIndex, std::string> FeatureIndexToName;
+  static const std::unordered_map<FeatureIndex, Scope> FeatureIndexToScope;
+  static const std::unordered_map<FeatureIndex, GroupID> FeatureIndexToGroup;
+  static const std::multimap<GroupID, FeatureIndex> GroupToFeatureIndices;
+  static const std::multimap<Scope, FeatureIndex> ScopeToFeatureIndices;
+  // The CalculateFeatureMap maps each feature to a corresponding function that
+  // calculates the feature and also sets the feature value inside
+  // FeatureValues field.
+  static const std::unordered_map<FeatureIndex, CalculateFeatureFunction>
+      CalculateFeatureMap;
+
+  // TODO:
+  // Implement the cache systems here. See similar example in DumpFeature.cpp
+  // Notice we've only cached the FunctionLevels.
+  // But in the future this should be generalized for all features.
+  // One way to do this is to define a map from FeatureIndex -> Mapping.
+  // Inside this mapping, the key should be the Scope and a set of analysis it
+  // depends on.
+
+  static std::map<const Function *, unsigned> FunctionLevels;
+
+  // Saved FeatureValues when we collect the features.
+  FeatureValueMap FeatureToValue;
+  FeatureInfoMap FeatureToInfo;
+  FeatureInfo GlobalFeatureInfo;
+};
+
+ACPOCollectFeatures::FeatureIndex operator+(ACPOCollectFeatures::FeatureIndex,
+                                            int);
+ACPOCollectFeatures::FeatureIndex operator-(ACPOCollectFeatures::FeatureIndex,
+                                            int);
+ACPOCollectFeatures::FeatureIndex &
+operator++(ACPOCollectFeatures::FeatureIndex &);
+ACPOCollectFeatures::FeatureIndex
+operator++(ACPOCollectFeatures::FeatureIndex &, int);
+
+} // namespace llvm
+#endif // LLVM_ANALYSIS_ACPOCOLLECTFEATURES_H
diff --git a/llvm/include/llvm/Analysis/ACPOMLInterface.h b/llvm/include/llvm/Analysis/ACPOMLInterface.h
new file mode 100644
index 000000000000..996f27ee32ba
--- /dev/null
+++ b/llvm/include/llvm/Analysis/ACPOMLInterface.h
@@ -0,0 +1,482 @@
+//===- ACPOMLInterface.h - AI-Enabled Continuous Program Optimization -----===//
+//
+// 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
+//
+// Copyright (C) 2021-2022. Huawei Technologies Co., Ltd. All rights reserved.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_ACPOML_INTERFACE_H
+#define LLVM_ANALYSIS_ACPOML_INTERFACE_H
+
+#include "llvm/Analysis/ACPOModelRunner.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/Support/Program.h"
+
+#include <cstddef>
+#include <ios>
+#include <memory>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace llvm {
+
+class ACPOModelRunner;
+
+// This is class for storing information about a model.
+class Model {
+public:
+  // Constructors
+  Model() : NumFeatures{1}, NumOutputs{1} {}
+  Model(std::size_t NumFeatures) : NumFeatures{NumFeatures}, NumOutputs{1} {}
+  Model(std::size_t NumFeatures, int NumOutputs)
+      : NumFeatures{NumFeatures}, NumOutputs{NumOutputs} {}
+
+  // Getters/Setters
+  std::size_t getNumFeatures() const { return NumFeatures; }
+  void setNumFeatures(int NumFeatures) { this->NumFeatures = NumFeatures; }
+
+  int getNumOutputs() const { return NumOutputs; }
+  void setNumOutputs(int NumOutputs) { this->NumOutputs = NumOutputs; }
+
+  std::string getSignature() const { return Signature; }
+  void setSignature(std::string Signature) { this->Signature = Signature; }
+
+  // Register a feature into the NametoID and IDToIndex maps.
+  bool registerFeature(std::string FeatureName, uint64_t FeatureID, int Index);
+
+  // Register an input into the map.
+  bool registerInput(std::string InputName, std::string InputType);
+
+  // Register an output into the map.
+  bool registerOutput(std::string OutputName, std::string OutputType);
+
+  // Return the index of a feature within the feature list used by inference().
+  int getIndex(uint64_t FeatureID) const;
+  int getIndex(std::string FeatureName) const;
+
+  // Return the name of a feature within the feature list used by inference().
+  std::string getName(uint64_t FeatureID) const;
+
+  // Return true if output name exists.
+  bool checkOutputExists(std::string OutputName) const;
+
+  // Return the type of an input given its name.
+  std::string getInputType(std::string OutputName) const;
+
+  // Return the type of an output given its name.
+  std::string getOutputType(std::string OutputName) const;
+
+private:
+  std::size_t NumFeatures;
+  int NumOutputs;
+  std::string Signature;
+  std::unordered_map<std::string, uint64_t> NameToID;
+  std::unordered_map<uint64_t, std::string> IDToName;
+  std::unordered_map<uint64_t, int> IDToIndex;
+
+  // A map from input name to input type
+  std::unordered_map<std::string, std::string> InputMap;
+
+  // A map from output name to output type
+  std::unordered_map<std::string, std::string> OutputMap;
+};
+
+// This is the base class to define an interface with an ML framework.
+class ACPOMLInterface {
+public:
+  // Constructor/Destructor.
+  ACPOMLInterface() {}
+  virtual ~ACPOMLInterface() {}
+
+  // Getters/Setters
+  bool isInitialized() const { return Initialized; }
+  void setInitialized(bool Val) { Initialized = Val; }
+
+  // Interface methods.
+  // Return the next available ID for a feature.
+  virtual uint64_t assignID() = 0;
+
+  // Load a model by reading from the specified file.
+  // Return false if the operation failed.
+  virtual bool loadModel(std::string ModelSpecFile) = 0;
+
+  // Insert a new model into the model map.
+  virtual bool registerModel(std::string ModelName, int NumFeatures) = 0;
+  virtual bool registerModel(std::string ModelName, int NumFeatures,
+                             int NumOutputs) = 0;
+
+  // Register a new feature for a given model.
+  virtual bool registerFeature(std::string ModelName, std::string FeatureName,
+                               int Index) = 0;
+
+  // Register a new output for a given model.
+  virtual bool registerOutput(std::string ModelName, std::string OutputName,
+                              std::string OutputType) = 0;
+
+  // Specify how many models are currently live in ML framework memory.
+  virtual int getNumLoadedModels() = 0;
+
+  // Specify the input file to use as IR to be passed to the model (however
+  // it is processed afterwards). Return false if the operation failed.
+  virtual bool defineInputIR(std::string Filename) = 0;
+
+  // Specify a custom feature for a model to use as input at the next model
+  // invocation. Return false if the operation failed.
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int64_t FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                double FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                float FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                bool FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int64_t FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                double FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                float FeatureValue) = 0;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                bool FeatureValue) = 0;
+
+  // Replace all features with the given feature values.
+  // Activate the specified model.
+  virtual bool initializeFeatures(
+      std::string ModelName,
+      const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) = 0;
+
+  virtual bool
+  initializeFeatures(std::string ModelName,
+                     const std::vector<std::pair<std::string, std::string>>
+                         &FeatureValues) = 0;
+
+  // Set features with the specified feature values.
+  // Not changing the currently active model.
+  virtual bool setCustomFeatures(
+      std::string ModelName,
+      const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) = 0;
+
+  virtual bool
+  setCustomFeatures(std::string ModelName,
+                    const std::vector<std::pair<std::string, std::string>>
+                        &FeatureValues) = 0;
+
+  // Run a model with specified name. Return false if the execution was not
+  // possible or an error was encountered.
+  virtual bool runModel(std::string ModelName) = 0;
+
+  // Return the type of an output within the model specified by the name.
+  virtual std::string getOutputType(std::string ModelName,
+                                    std::string OutputName) = 0;
+
+  // Return model results, based on the output name.
+  virtual int getModelResultI(std::string OutputName) = 0;
+  virtual int64_t getModelResultI64(std::string OutputName) = 0;
+  virtual float getModelResultF(std::string OutputName) = 0;
+  virtual double getModelResultD(std::string OutputName) = 0;
+  virtual bool getModelResultB(std::string OutputName) = 0;
+
+  // Get status of the ML interface. Return zero if succeeded.
+  virtual int getStatus() = 0;
+
+  // Free up memory taken by a model.
+  virtual bool releaseModel(std::string ModelName) = 0;
+
+  // Close interface when done. Return false if the command was not successful.
+  // In some cases this just requires a constructor for this class to be called,
+  // but in others, additional steps that require feedback may be needed.
+  virtual bool closeMLInterface() = 0;
+
+  // Set a flag to invoke closeMLInterface when the instance of the class is
+  // destroyed.
+  void setCloseOnDestruction() { CloseOnDestruction = true; }
+
+protected:
+  bool CloseOnDestruction = false;
+
+private:
+  bool Initialized = false;
+};
+
+class ACPOMLPythonInterface : public ACPOMLInterface {
+public:
+  ACPOMLPythonInterface();
+  virtual ~ACPOMLPythonInterface();
+
+  // Interface methods.
+  // Return the next available ID for a feature.
+  virtual uint64_t assignID() override;
+
+  // Load a model by reading from the specified file.
+  // Return false if the operation failed.
+  virtual bool loadModel(std::string ModelSpecFile) override;
+
+  // Insert a new model into the model map.
+  virtual bool registerModel(std::string ModelName, int NumFeatures) override;
+  virtual bool registerModel(std::string ModelName, int NumFeatures,
+                             int NumOutputs) override;
+
+  // Register a new feature for a given model.
+  virtual bool registerFeature(std::string ModelName, std::string FeatureName,
+                               int Index) override;
+
+  // Register a new output for a given model.
+  virtual bool registerOutput(std::string ModelName, std::string OutputName,
+                              std::string OutputType) override;
+
+  // Specify how many models are currently live in ML framework memory.
+  virtual int getNumLoadedModels() override;
+
+  // Specify the input file to use as IR to be passed to the model (however
+  // it is processed afterwards). Return false if the operation failed.
+  virtual bool defineInputIR(std::string Filename) override;
+
+  // Specify a custom feature for a model to use as input at the next model
+  // invocation. Return false if the operation failed.
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int64_t FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                double FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                float FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                bool FeatureValue) override;
+
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int64_t FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                double FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                float FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                bool FeatureValue) override;
+
+  // Replace all features with the given feature values.
+  // Activate the specified model.
+  virtual bool
+  initializeFeatures(std::string ModelName,
+                     const std::vector<std::pair<uint64_t, std::string>>
+                         &FeatureValues) override;
+
+  virtual bool
+  initializeFeatures(std::string ModelName,
+                     const std::vector<std::pair<std::string, std::string>>
+                         &FeatureValues) override;
+
+  // Set features with the specified feature values.
+  // Not changing the currently active model.
+  virtual bool
+  setCustomFeatures(std::string ModelName,
+                    const std::vector<std::pair<uint64_t, std::string>>
+                        &FeatureValues) override;
+
+  virtual bool
+  setCustomFeatures(std::string ModelName,
+                    const std::vector<std::pair<std::string, std::string>>
+                        &FeatureValues) override;
+
+  // Run a model with the specified name. Return false if the execution was not
+  // possible or an error was encountered.
+  virtual bool runModel(std::string ModelName) override;
+
+  // Return the type of an output within the model specified by the name.
+  virtual std::string getOutputType(std::string ModelName,
+                                    std::string OutputName) override;
+
+  // Return model results, based on the output name.
+  virtual int getModelResultI(std::string OutputName) override;
+  virtual int64_t getModelResultI64(std::string OutputName) override;
+  virtual float getModelResultF(std::string OutputName) override;
+  virtual double getModelResultD(std::string OutputName) override;
+  virtual bool getModelResultB(std::string OutputName) override;
+
+  // Get status of the ML interface. Return zero if succeeded.
+  virtual int getStatus() override;
+
+  // Free up memory taken by a model.
+  virtual bool releaseModel(std::string ModelName) override;
+
+  // Close interface when done. Return false if the command was not successful.
+  // In some cases this just requires a constructor for this class to be called,
+  // but in others, additional steps that require feedback may be needed.
+  virtual bool closeMLInterface() override;
+
+protected:
+  void sendCommand(const std::string &Command);
+  void sendCommand(const std::vector<std::string> &Features);
+  std::string getResponse();
+  std::vector<std::string> tokenize(const std::string &Line);
+
+private:
+  llvm::sys::ProcessInfo SubProcess;
+  FILE *PipeOut = nullptr;
+  FILE *PipeIn = nullptr;
+
+  uint64_t NextID;
+
+  std::string CurrentlyActiveModel;
+
+  // Mapping model names to their corresponding Model
+  std::unordered_map<std::string, std::shared_ptr<Model>> ModelMap;
+};
+
+std::shared_ptr<ACPOMLInterface> createPersistentPythonMLIF();
+
+class ACPOMLCPPInterface : public ACPOMLInterface {
+public:
+  ACPOMLCPPInterface();
+  virtual ~ACPOMLCPPInterface();
+
+  // Interface methods.
+  // Return the next available ID for a feature.
+  virtual uint64_t assignID() override;
+
+  // Load a model by reading from the specified file.
+  // Return false if the operation failed.
+  // For ACPOMLCompiledInterface, loadCompiledModel() should be used instead.
+  virtual bool loadModel(std::string ModelSpecFile) override;
+
+  // Insert a new model into the model map.
+  virtual bool registerModel(std::string ModelName, int NumFeatures) override;
+  virtual bool registerModel(std::string ModelName, int NumFeatures,
+                             int NumOutputs) override;
+
+  // Register a new feature for a given model.
+  virtual bool registerFeature(std::string ModelName, std::string FeatureName,
+                               int Index) override;
+
+  // Register a new output for a given model.
+  virtual bool registerOutput(std::string ModelName, std::string OutputName,
+                              std::string OutputType) override;
+
+  // Specify how many models are currently live in ML framework memory.
+  virtual int getNumLoadedModels() override;
+
+  // Specify the input file to use as IR to be passed to the model (however
+  // it is processed afterwards). Return false if the operation failed.
+  virtual bool defineInputIR(std::string Filename) override;
+
+  // Specify a custom feature for a model to use as input at the next model
+  // invocation. Return false if the operation failed.
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                int64_t FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                double FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                float FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, uint64_t FeatureID,
+                                bool FeatureValue) override;
+
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                int64_t FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                double FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                float FeatureValue) override;
+  virtual bool setCustomFeature(std::string ModelName, std::string FeatureName,
+                                bool FeatureValue) override;
+
+  // Replace all features with the given feature values.
+  // Activate the specified model.
+  virtual bool
+  initializeFeatures(std::string ModelName,
+                     const std::vector<std::pair<uint64_t, std::string>>
+                         &FeatureValues) override;
+
+  virtual bool
+  initializeFeatures(std::string ModelName,
+                     const std::vector<std::pair<std::string, std::string>>
+                         &FeatureValues) override;
+
+  // Set features with the specified feature values.
+  // Not changing the currently active model.
+  virtual bool
+  setCustomFeatures(std::string ModelName,
+                    const std::vector<std::pair<uint64_t, std::string>>
+                        &FeatureValues) override;
+
+  virtual bool
+  setCustomFeatures(std::string ModelName,
+                    const std::vector<std::pair<std::string, std::string>>
+                        &FeatureValues) override;
+
+  // Run a model with the specified name. Return false if the execution was not
+  // possible or an error was encountered.
+  virtual bool runModel(std::string ModelName) override;
+
+  // Return the type of an input within the model specified by the name.
+  virtual std::string getInputType(std::string ModelName,
+                                   std::string InputName);
+
+  // Return the type of an output within the model specified by the name.
+  virtual std::string getOutputType(std::string ModelName,
+                                    std::string OutputName) override;
+
+  // Return model results, based on the output name.
+  virtual int getModelResultI(std::string OutputName) override;
+  virtual int64_t getModelResultI64(std::string OutputName) override;
+  virtual float getModelResultF(std::string OutputName) override;
+  virtual double getModelResultD(std::string OutputName) override;
+  virtual bool getModelResultB(std::string OutputName) override;
+
+  // Get status of the ML interface. Return zero if succeeded.
+  virtual int getStatus() override;
+
+  // Free up memory taken by a model.
+  virtual bool releaseModel(std::string ModelName) override;
+
+  // Close interface when done. Return false if the command was not successful.
+  // In some cases this just requires a constructor for this class to be called,
+  // but in others, additional steps that require feedback may be needed.
+  virtual bool closeMLInterface() override;
+
+private:
+  uint64_t NextID;
+
+  std::string CurrentlyActiveModel;
+
+  // Mapping model names to their corresponding Model
+  std::unordered_map<std::string, std::shared_ptr<Model>> ModelMap;
+
+  // Mapping model names to their corresponding Runner
+  std::unordered_map<std::string, std::shared_ptr<ACPOModelRunner>> RunnerMap;
+
+  std::string readModelParam(std::string FilePath, std::string Param);
+
+  void readFeatures(std::string FilePath,
+                    std::vector<std::pair<std::string, std::string>> &Features);
+  void readOutputs(std::string FilePath,
+                   std::vector<std::pair<std::string, std::string>> &Outputs);
+
+  typedef std::unique_ptr<ACPOModelRunner> (*CreateModelRunnerFunction)(
+      std::vector<std::pair<std::string, std::string>>,
+      StringRef); // function pointer type
+  const static std::unordered_map<std::string, CreateModelRunnerFunction>
+      CreateModelRunnerMap;
+};
+
+std::shared_ptr<ACPOMLInterface> createPersistentCompiledMLIF();
+
+} // namespace llvm
+
+#endif // LLVM_ANALYSIS_ACPOML_INTERFACE_H
diff --git a/llvm/include/llvm/Analysis/ACPOModel.h b/llvm/include/llvm/Analysis/ACPOModel.h
new file mode 100644
index 000000000000..34dbc0fdb8bf
--- /dev/null
+++ b/llvm/include/llvm/Analysis/ACPOModel.h
@@ -0,0 +1,122 @@
+//===- ACPOModel.h - AI-Enabled Continuous Program Optimization -----------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_ACPOMODEL_H
+#define LLVM_ANALYSIS_ACPOMODEL_H
+
+#include "llvm/Analysis/ACPOMLInterface.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Type.h"
+#include <map>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+
+namespace llvm {
+
+class OptimizationRemarkEmitter;
+class LLVMContext;
+
+class ACPOAdvice {
+public:
+  struct FieldType {
+    Type::TypeID T;
+    Constant *Val;
+  };
+
+  ACPOAdvice() {}
+  ACPOAdvice(std::unique_ptr<ACPOAdvice> &ResultFormat);
+  virtual ~ACPOAdvice() {};
+
+  Constant *getField(std::string name) {
+    auto Search = FieldMap.find(name);
+    if (Search == FieldMap.end()) {
+      return nullptr;
+    }
+    return Search->second.Val;
+  }
+
+  void reserveField(std::string name, Type::TypeID &ID) {
+    FieldMap[name].T = ID;
+    FieldMap[name].Val = nullptr;
+  }
+
+  void addField(std::string name, Constant *Val) {
+    assert(Val != nullptr);
+    FieldMap[name].T = Val->getType()->getTypeID();
+    FieldMap[name].Val = Val;
+  }
+
+  std::unordered_map<std::string, struct FieldType> &getFieldMap() {
+    return FieldMap;
+  }
+
+private:
+  std::unordered_map<std::string, struct FieldType> FieldMap;
+};
+
+class ACPOModel {
+public:
+  ACPOModel(OptimizationRemarkEmitter *OptReEm, bool UseML = true) :
+      ORE(OptReEm), ShouldUseML(UseML) {
+    ResultFormat = std::make_unique<ACPOAdvice>();
+    assert(ResultFormat != nullptr);
+  }
+
+  ~ACPOModel() {}
+
+  bool isForcedToStop() const { return ForceStop; }
+
+  // This is a interface method to return result of estimation either via an ML
+  // model or by employing a heuristic. The ML version should be implemented in
+  // the getAdviceML, which can be overwritten when necessary. The non-ML
+  // version should be implemented in getAdviceNoML and that should always be
+  // overwritten (and it will be marked as pure (=0) to force the programmer
+  // to do so).
+  std::unique_ptr<ACPOAdvice> getAdvice();
+  void addRequiredResultField(std::string name, Type::TypeID &ID);
+
+  void setContextPtr(LLVMContext *C) { Context = C; }
+  LLVMContext *getContextPtr() { return Context; }
+
+  void setMLIF(std::shared_ptr<ACPOMLInterface> ML) { MLIF = ML; }
+  std::shared_ptr<ACPOMLInterface> getMLIF() { return MLIF; }
+
+protected:
+  void addFeature(int64_t ID, Constant *Val);
+  virtual void sendCustomFeatures() {}
+  virtual void prepareModelInput();
+  virtual bool runModel(std::unique_ptr<ACPOAdvice> &Result);
+
+  virtual std::unique_ptr<ACPOAdvice> getAdviceML();
+  virtual std::unique_ptr<ACPOAdvice> getAdviceNoML() = 0;
+
+private:
+  // Pointer to means of feedback propagation
+  OptimizationRemarkEmitter *ORE;
+
+  // We may need LLVMContext to set values of a Constant
+  LLVMContext *Context = nullptr;
+
+  // Specify expected format of the ACPOAdvice result.
+  std::unique_ptr<ACPOAdvice> ResultFormat = nullptr;
+
+  // Custom feature list.
+  std::unordered_map<uint64_t, Constant *> CustomFeatureMap;
+
+  // Interface to ML framework.
+  std::shared_ptr<ACPOMLInterface> MLIF = nullptr;
+
+  // Specify if ML infra is in use
+  bool ShouldUseML = false;
+  bool ForceStop = false;
+};
+
+} // namespace llvm
+
+#endif // LLVM_ANALYSIS_ACPOMODEL_H
diff --git a/llvm/include/llvm/Analysis/ACPOModelRunner.h b/llvm/include/llvm/Analysis/ACPOModelRunner.h
new file mode 100644
index 000000000000..819e17f71103
--- /dev/null
+++ b/llvm/include/llvm/Analysis/ACPOModelRunner.h
@@ -0,0 +1,39 @@
+//===- ACPOModelRunner.h - AI-Enabled Continuous Program Optimization -----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_ACPOMODEL_H
+#define LLVM_ANALYSIS_ACPOMODEL_H
+
+#include "llvm/Analysis/MLModelRunner.h"
+
+namespace llvm {
+
+class ACPOModelRunner : public MLModelRunner {
+public:
+  virtual bool setCustomFeature(int FeatureIndex, int FeatureValue) = 0;
+  virtual bool setCustomFeature(int FeatureIndex, int64_t FeatureValue) = 0;
+  virtual bool setCustomFeature(int FeatureIndex, double FeatureValue) = 0;
+  virtual bool setCustomFeature(int FeatureIndex, float FeatureValue) = 0;
+  virtual bool setCustomFeature(int FeatureIndex, bool FeatureValue) = 0;
+
+  virtual bool runModel() = 0;
+
+  virtual int getModelResultI(std::string OutputName) = 0;
+  virtual int64_t getModelResultI64(std::string OutputName) = 0;
+  virtual float getModelResultF(std::string OutputName) = 0;
+  virtual double getModelResultD(std::string OutputName) = 0;
+  virtual bool getModelResultB(std::string OutputName) = 0;
+
+protected:
+  ACPOModelRunner(LLVMContext &Ctx, size_t NrInputs)
+      : MLModelRunner(Ctx, MLModelRunner::Kind::Release, NrInputs) {}
+};
+
+} // namespace llvm
+
+#endif // LLVM_ANALYSIS_ACPOMODEL_H
diff --git a/llvm/include/llvm/Analysis/AOTModelRunner.h b/llvm/include/llvm/Analysis/AOTModelRunner.h
new file mode 100644
index 000000000000..abc6258c4f09
--- /dev/null
+++ b/llvm/include/llvm/Analysis/AOTModelRunner.h
@@ -0,0 +1,203 @@
+//===- AOTModelRunner.h - AI-Enabled Continuous Program Optimization ------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_AOTMODEL_H
+#define LLVM_ANALYSIS_AOTMODEL_H
+
+#include "llvm/Analysis/ACPOModelRunner.h"
+#include "llvm/Analysis/TensorSpec.h"
+
+#define DEBUG_TYPE "acpo-aot"
+
+namespace llvm {
+
+template <class TGen> class AOTModelRunner : public ACPOModelRunner {
+public:
+  /// FeatureNames' type should be an indexed collection of std::string, like
+  /// std::array or std::vector, that has a size() method.
+  /// In the future, this method could be expanded to allow AOT models with
+  /// multiple outputs, by taking in a vector of string pairs similar to the
+  /// Features vector.
+  /// The current implementation does work for AOT models with a single output
+  /// which is a vector (or higher-dimensional tensor) of multiple values.
+  AOTModelRunner(
+      LLVMContext &Ctx,
+      const std::vector<std::pair<std::string, std::string>> &Features,
+      StringRef DecisionName, StringRef FeedPrefix = "feed_",
+      StringRef FetchPrefix = "fetch_")
+      : ACPOModelRunner(Ctx, Features.size()),
+        CompiledModel(std::make_unique<TGen>()) {
+    assert(CompiledModel && "The CompiledModel should be valid");
+
+    for (size_t I = 0; I < Features.size(); ++I) {
+      const int Index =
+          CompiledModel->LookupArgIndex(FeedPrefix.str() + Features[I].first);
+      void *Buffer = nullptr;
+      if (Index >= 0) {
+        Buffer = CompiledModel->arg_data(Index);
+      } else {
+        LLVM_DEBUG(dbgs() << "Warning: AOTModelRunner was unable to find the "
+                             "feature "
+                          << (FeedPrefix.str() + Features[I].first)
+                          << " in the compiled model\n");
+      }
+      // The order of features passed to the model runner is important, it
+      // determines their index
+      TensorSpec Spec = makeSpec(Features[I].first, Features[I].second);
+      setUpBufferForTensor(I, Spec, Buffer);
+    }
+
+    ResultIndex = CompiledModel->LookupResultIndex(FetchPrefix.str() +
+                                                   DecisionName.str());
+    assert(ResultIndex >= 0 && "Cannot find DecisionName in inlining model");
+  }
+
+  virtual ~AOTModelRunner() = default;
+
+  static bool classof(const ACPOModelRunner *R) {
+    return R->getKind() == ACPOModelRunner::Kind::Release;
+  }
+
+  bool setCustomFeature(int FeatureIndex, int FeatureValue) override {
+    LLVM_DEBUG(dbgs() << "AOTModelRunner: setting int feature " << FeatureIndex
+                      << " to " << FeatureValue << "\n");
+    *getTensor<int>(FeatureIndex) = FeatureValue;
+    return true;
+  }
+  bool setCustomFeature(int FeatureIndex, int64_t FeatureValue) override {
+    LLVM_DEBUG(dbgs() << "AOTModelRunner: setting int64 feature "
+                      << FeatureIndex << " to " << FeatureValue << "\n");
+    *getTensor<int64_t>(FeatureIndex) = FeatureValue;
+    return true;
+  }
+  bool setCustomFeature(int FeatureIndex, double FeatureValue) override {
+    LLVM_DEBUG(dbgs() << "AOTModelRunner: setting double feature "
+                      << FeatureIndex << " to " << FeatureValue << "\n");
+    *getTensor<double>(FeatureIndex) = FeatureValue;
+    return true;
+  }
+  bool setCustomFeature(int FeatureIndex, float FeatureValue) override {
+    LLVM_DEBUG(dbgs() << "AOTModelRunner: setting float feature "
+                      << FeatureIndex << " to " << FeatureValue << "\n");
+    *getTensor<float>(FeatureIndex) = FeatureValue;
+    return true;
+  }
+  bool setCustomFeature(int FeatureIndex, bool FeatureValue) override {
+    // There are no bool tensors, so assume int for now
+    LLVM_DEBUG(dbgs() << "AOTModelRunner: setting bool feature " << FeatureIndex
+                      << " to " << FeatureValue << "\n");
+    *getTensor<int>(FeatureIndex) = FeatureValue;
+    return true;
+  }
+
+  bool runModel() override {
+    CompiledModel->Run();
+    return true;
+  }
+
+  int getModelResultI(std::string OutputName) override {
+    void *Data = CompiledModel->result_data(ResultIndex);
+    int Result = *reinterpret_cast<int *>(Data);
+    LLVM_DEBUG(dbgs() << "Returning int model result " << OutputName << " = "
+                      << Result << "\n");
+    return Result;
+  }
+
+  int64_t getModelResultI64(std::string OutputName) override {
+    void *Data = CompiledModel->result_data(ResultIndex);
+    int64_t Result = *reinterpret_cast<int64_t *>(Data);
+    LLVM_DEBUG(dbgs() << "Returning int64 model result " << OutputName << " = "
+                      << Result << "\n");
+    return Result;
+  }
+
+  float getModelResultF(std::string OutputName) override {
+    void *Data = CompiledModel->result_data(ResultIndex);
+    float Result = *reinterpret_cast<float *>(Data);
+    LLVM_DEBUG(dbgs() << "Returning float model result " << OutputName << " = "
+                      << Result << "\n");
+    return Result;
+  }
+
+  double getModelResultD(std::string OutputName) override {
+    void *Data = CompiledModel->result_data(ResultIndex);
+    double Result = *reinterpret_cast<double *>(Data);
+    LLVM_DEBUG(dbgs() << "Returning double model result " << OutputName << " = "
+                      << Result << "\n");
+    return Result;
+  }
+
+  bool getModelResultB(std::string OutputName) override {
+    // Since there are no bool tensors, use int and return the corresponding
+    // result
+    void *Data = CompiledModel->result_data(ResultIndex);
+    bool Result = (*reinterpret_cast<int *>(Data)) > 0;
+    LLVM_DEBUG(dbgs() << "Returning bool model result " << OutputName << " = "
+                      << Result << "\n");
+    return Result;
+  }
+
+protected:
+  std::unique_ptr<TGen> CompiledModel;
+
+private:
+  void *evaluateUntyped() override {
+    CompiledModel->Run();
+    return CompiledModel->result_data(ResultIndex);
+  }
+
+  llvm::TensorSpec makeSpec(std::string Name, std::string Type) {
+    std::vector<int64_t> Shape{};
+    // If the string is of the form "float32[7][8]", read the value in brackets
+    // as the shape (read from left to right)
+    size_t RightBracket = 0;
+    size_t LeftBracket = 0;
+    do {
+      LeftBracket = Type.find("[", RightBracket + 1);
+      if (LeftBracket == std::string::npos) {
+        break;
+      }
+      RightBracket = Type.find("]", LeftBracket + 1);
+      size_t Value = std::stol(
+          Type.substr(LeftBracket + 1, RightBracket - LeftBracket - 1));
+      Shape.push_back(Value);
+    } while (RightBracket != std::string::npos);
+
+    // Remove array indices to just get type
+    if (Type.find("[") != std::string::npos) {
+      Type = Type.substr(0, Type.find("["));
+    }
+
+    if (Shape.size() == 0)
+      Shape.push_back(1); // Default shape is {1}
+
+    if (Type == "int64") {
+      return TensorSpec::createSpec<int64_t>(Name, Shape);
+    }
+    if (Type == "int32") {
+      return TensorSpec::createSpec<int32_t>(Name, Shape);
+    }
+    if (Type == "int" || Type == "bool") {
+      // There are no bool tensors, so assume int for now
+      return TensorSpec::createSpec<int>(Name, Shape);
+    }
+    if (Type == "float64") {
+      return TensorSpec::createSpec<double>(Name, Shape);
+    }
+    if (Type == "float32") {
+      return TensorSpec::createSpec<float>(Name, Shape);
+    }
+    assert(false && "ACPO AOT: received unknown feature type");
+  }
+
+  int32_t ResultIndex = -1;
+};
+
+} // namespace llvm
+
+#endif // LLVM_ANALYSIS_AOTMODEL_H
diff --git a/llvm/include/llvm/Analysis/CallHeight.h b/llvm/include/llvm/Analysis/CallHeight.h
new file mode 100644
index 000000000000..c1251081f525
--- /dev/null
+++ b/llvm/include/llvm/Analysis/CallHeight.h
@@ -0,0 +1,72 @@
+//===- CallHeight.h - Call height for function ------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This header file defines passes to get the call height of functions.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_CALLHEIGHT
+#define LLVM_ANALYSIS_CALLHEIGHT
+
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Pass.h"
+
+#include <unordered_map>
+#include <map>
+
+namespace llvm {
+
+class CallHeight {
+private:
+  /// Map from function to its level (callheight)
+  std::unique_ptr<std::map<const Function *, unsigned>> Levels;
+
+public:
+  CallHeight(Module &M);
+
+  // Change this to getHeight
+  unsigned getLevel(Function &F);
+
+  bool invalidate(Module &, const PreservedAnalyses &PA,
+                  ModuleAnalysisManager::Invalidator &) {
+    return false;
+  }
+};
+
+/// This analysis computes the mapping from function to level (callheight)
+/// for MLInliner
+class CallHeightAnalysis : public AnalysisInfoMixin<CallHeightAnalysis> {
+public:
+  static AnalysisKey Key;
+  using Result = CallHeight;
+
+  Result run(Module &M, ModuleAnalysisManager &MAM);
+};
+
+/// Legacy wrapper pass to provide the CallHeightAnalysis object.
+class CallHeightAnalysisWrapper : public ModulePass {
+  std::unique_ptr<llvm::CallHeight> Result;
+
+public:
+  static char ID;
+
+  CallHeightAnalysisWrapper() : ModulePass(ID) {}
+
+  bool runOnModule(Module &M) override;
+
+  llvm::CallHeight &getResult() { return *Result; }
+  const llvm::CallHeight &getResult() const { return *Result; }
+  void getAnalysisUsage(AnalysisUsage &AU) const override;
+};
+
+Pass *createCallHeightAnalysisWrapper();
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/Analysis/DumpCallsite.h b/llvm/include/llvm/Analysis/DumpCallsite.h
new file mode 100644
index 000000000000..9f80fe1cb985
--- /dev/null
+++ b/llvm/include/llvm/Analysis/DumpCallsite.h
@@ -0,0 +1,27 @@
+//===- DumpCallSite.h - Dump information about a callsite -------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This header file defines the pass used to dump a callsite.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_DUMPCALLSITE
+#define LLVM_ANALYSIS_DUMPCALLSITE
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class DumpCallsitePass : public PassInfoMixin<DumpCallsitePass> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+};
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/Analysis/DumpFeature.h b/llvm/include/llvm/Analysis/DumpFeature.h
new file mode 100644
index 000000000000..226e06cf5600
--- /dev/null
+++ b/llvm/include/llvm/Analysis/DumpFeature.h
@@ -0,0 +1,194 @@
+//===- DumpFeature.h - Dump features for a function -------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This header file defines passes to dump features for functions in an scc.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_DUMPFEATURE
+#define LLVM_ANALYSIS_DUMPFEATURE
+
+#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/Analysis/CGSCCPassManager.h"
+#include "llvm/Analysis/CallGraph.h"
+#include "llvm/Analysis/CallGraphSCCPass.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/IR/Dominators.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Pass.h"
+
+#include <map>
+
+// EnableFeatureDump - This boolean is set to true if '-enable-feature-dump' is
+// used as command line option. And we dump function features.
+extern bool EnableFeatureDump;
+
+namespace llvm {
+
+class DumpFeaturePass : public PassInfoMixin<DumpFeaturePass> {
+public:
+  PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
+                        LazyCallGraph &CG, CGSCCUpdateResult &UR);
+
+private:
+  /// Get the caller height from cache or calculate from scratch
+  /// for a specific function F
+  int getCallHeight(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
+                    LazyCallGraph &CG, Function *F);
+};
+
+class ACPOFIExtendedFeatures {
+public:
+  enum class NamedFeatureIndex : size_t {
+    InitialSize,
+    Blocks,
+    Calls,
+    IsLocal,
+    IsLinkOnceODR,
+    IsLinkOnce,
+    Loops,
+    MaxLoopDepth,
+    MaxDomTreeLevel,
+    PtrArgs,
+    PtrCallee,
+    CallReturnPtr,
+    ConditionalBranch,
+    CBwithArg,
+    CallerHeight,
+    CallUsage,
+    IsRecursive,
+    NumCallsiteInLoop,
+    NumOfCallUsesInLoop,
+    EntryBlockFreq,
+    MaxCallsiteBlockFreq,
+    NumNamedFeatures
+  };
+
+  enum class NamedFloatFeatureIndex : size_t {
+    InstructionPerBlock,
+    SuccessorPerBlock,
+    AvgVecInstr,
+    AvgNestedLoopLevel,
+    InstrPerLoop,
+    BlockWithMultipleSuccecorsPerLoop,
+    NumNamedFloatFeatures
+  };
+
+  struct FunctionFeatures {
+    static const size_t FeatureCount;
+
+    std::array<uint64_t,
+               static_cast<size_t>(NamedFeatureIndex::NumNamedFeatures)>
+        NamedFeatures = {{0}};
+    std::array<float, static_cast<size_t>(
+                          NamedFloatFeatureIndex::NumNamedFloatFeatures)>
+        NamedFloatFeatures = {{0}};
+    std::vector<int32_t> InstructionHistogram;
+    std::vector<int32_t> InstructionPairHistogram;
+
+    void fillTensor(int32_t *Ptr) const;
+    uint64_t &operator[](NamedFeatureIndex Pos) {
+      return NamedFeatures[static_cast<size_t>(Pos)];
+    }
+    float &operator[](NamedFloatFeatureIndex Pos) {
+      return NamedFloatFeatures[static_cast<size_t>(Pos)];
+    }
+  };
+
+  ACPOFIExtendedFeatures() = default;
+
+  // Collect a number of features from the function F
+  static FunctionFeatures getFunctionFeatures(
+      Function &F, DominatorTree &DomTree, TargetTransformInfo &TTI,
+      LoopInfo &LI, FunctionAnalysisManager *FAM = nullptr, bool ValidSize = false,
+      bool ValidLoop = false, bool ValidTree = false);
+
+private:
+  // Loop related features, will update FF
+  static void updateLoopRelatedFeatures(Function &F, LoopInfo &LI,
+                                        FunctionFeatures &FF);
+  // Instruction and BasicBlock related features, will update FF
+  static void updateInstBBRelatedFeatures(Function &F, FunctionFeatures &FF);
+
+  // This function should mimic the behaviour of updating all features below at
+  // once:
+  //    getMaxCallsiteBlockFreq
+  //    updateCallsiteRelatedFeatures
+  //    updateInstBBRelatedFeatures
+  static void
+  updateBBLoopCallsiteBFFeatures(Function &F, FunctionFeatures &FF,
+                                 LoopInfo &LI,
+                                 FunctionAnalysisManager *FAM = nullptr);
+};
+
+const std::map<ACPOFIExtendedFeatures::NamedFeatureIndex, std::string>
+    NamedFeatureIndexToName = {
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize, "InitialSize"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::Blocks, "Blocks"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::Calls, "Calls"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::IsLocal, "IsLocal"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnceODR,
+         "IsLinkOnceODR"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnce, "IsLinkOnce"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::Loops, "Loops"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth,
+         "MaxLoopDepth"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel,
+         "MaxDomTreeLevel"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::PtrArgs, "PtrArgs"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::PtrCallee, "PtrCallee"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::CallReturnPtr,
+         "CallReturnPtr"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::ConditionalBranch,
+         "ConditionalBranch"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::CBwithArg, "CBwithArg"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::CallerHeight,
+         "CallerHeight"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::CallUsage, "CallUsage"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::IsRecursive, "IsRecursive"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::NumCallsiteInLoop,
+         "NumCallsiteInLoop"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::NumOfCallUsesInLoop,
+         "NumOfCallUsesInLoop"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::EntryBlockFreq,
+         "EntryBlockFreq"},
+        {ACPOFIExtendedFeatures::NamedFeatureIndex::MaxCallsiteBlockFreq,
+         "MaxCallsiteBlockFreq"}};
+
+const std::map<ACPOFIExtendedFeatures::NamedFloatFeatureIndex, std::string>
+    FloatFeatureIndexToName = {
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstructionPerBlock,
+         "InstructionPerBlock"},
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::SuccessorPerBlock,
+         "SuccessorPerBlock"},
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgVecInstr,
+         "AvgVecInstr"},
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel,
+         "AvgNestedLoopLevel"},
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop,
+         "InstrPerLoop"},
+        {ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+             BlockWithMultipleSuccecorsPerLoop,
+         "BlockWithMultipleSuccecorsPerLoop"}};
+
+ACPOFIExtendedFeatures::NamedFeatureIndex &
+operator++(ACPOFIExtendedFeatures::NamedFeatureIndex &n);
+
+ACPOFIExtendedFeatures::NamedFeatureIndex
+operator++(ACPOFIExtendedFeatures::NamedFeatureIndex &n, int);
+
+ACPOFIExtendedFeatures::NamedFloatFeatureIndex &
+operator++(ACPOFIExtendedFeatures::NamedFloatFeatureIndex &n);
+
+ACPOFIExtendedFeatures::NamedFloatFeatureIndex
+operator++(ACPOFIExtendedFeatures::NamedFloatFeatureIndex &n, int);
+
+} // namespace llvm
+
+#endif
diff --git a/llvm/include/llvm/Analysis/LoopInfo.h b/llvm/include/llvm/Analysis/LoopInfo.h
index 9be3e056cf76..ea4cb7f7c684 100644
--- a/llvm/include/llvm/Analysis/LoopInfo.h
+++ b/llvm/include/llvm/Analysis/LoopInfo.h
@@ -386,6 +386,17 @@ public:
   void dump() const;
   void dumpVerbose() const;
 
+#if defined(ENABLE_ACPO)
+  /// Print loop IR wrapped in a dummy function
+  void printWithFunctionWrapper(raw_ostream &ROS, Function *F,
+                                ArrayRef<BasicBlock *> LoopBlocks,
+                                BasicBlock *Header,
+                                SmallVector<BasicBlock *, 8> ExitBlocks,
+                                AssemblyAnnotationWriter *AAW,
+                                bool ShouldPreserveUseListOrder,
+                                bool IsForDebug) const;
+#endif
+
   /// Return the debug location of the start of this loop.
   /// This looks for a BB terminating instruction with a known debug
   /// location by looking at the preheader and header blocks. If it
diff --git a/llvm/include/llvm/Analysis/ModelDataCollector.h b/llvm/include/llvm/Analysis/ModelDataCollector.h
new file mode 100644
index 000000000000..ad3fc476a9b2
--- /dev/null
+++ b/llvm/include/llvm/Analysis/ModelDataCollector.h
@@ -0,0 +1,108 @@
+//===- ModelDataCollector.h - Data collector for ML model -----------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ANALYSIS_MODELDATACOLLECTOR_H
+#define LLVM_ANALYSIS_MODELDATACOLLECTOR_H
+
+#if defined(ENABLE_ACPO)
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Analysis/ACPOCollectFeatures.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Support/FormattedStream.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+#include <vector>
+
+namespace llvm {
+class ModelDataCollector {
+public:
+  enum DumpOption { function, loop, before, after };
+
+  ModelDataCollector(formatted_raw_ostream &OS, std::string OutputFileName = "")
+      : OutputFileName(OutputFileName), Out(OS) {}
+
+  ~ModelDataCollector() {}
+
+  std::string getDumpOptionAsString(DumpOption DO);
+  std::string getIRFileName(StringRef Key);
+  std::string getOutputFileName();
+  bool isEmptyOutputFile();
+  //std::string generateIRFileName(autotuning::CodeRegion CR);
+  std::string demangleName(const std::string &Name);
+  std::vector<std::pair<std::string, std::string>> getFeatures();
+  std::unique_ptr<raw_ostream>
+  createFile(const Twine &FilePath, const Twine &FileName, std::error_code &EC);
+  StringMap<std::string> getIRFileNameMap();
+  void
+  setFeatures(std::vector<std::pair<std::string, std::string>> NewFeatures);
+  void setIRFileNameMap(StringMap<std::string> IRFileNameMap);
+  void
+  addFeatures(std::vector<std::pair<std::string, std::string>> NewFeatures);
+
+  // Print out the features
+  void printRow(bool printHeader = false);
+
+  // Create the directory structure and store IR files in their corresponding
+  // directory
+  void writeIR(Loop *L, Function *F, std::string NewIRFileName,
+               std::string PassName, DumpOption DumpBeforeOrAfter,
+               bool PrintLoop, bool PrintFunction,
+               bool OverwriteIRFile = false);
+
+  // Print the loop IR to a file
+  void createIRFileForLoop(Loop *L, const Twine &IRFilePath,
+                           const Twine &NewIRFileName, bool OverwriteIRFile);
+
+  // Print the function IR to a file
+  void createIRFileForFunction(Function *F, const Twine &IRFilePath,
+                               const Twine &NewIRFileName,
+                               bool OverwriteIRFile);
+
+  virtual void collectFeatures(Loop *L, const std::string &ModuleName,
+                               const std::string &FuncName,
+                               const std::string &LoopName);
+
+  virtual void collectFeatures();
+
+  // FeatureCollectInfo contains the information of registered feature.
+  struct FeatureCollectInfo {
+    std::unique_ptr<ACPOCollectFeatures::FeaturesInfo> FeaturesInfo;
+    std::unique_ptr<ACPOCollectFeatures::Scopes> RegisteredScopes;
+    std::unique_ptr<ACPOCollectFeatures::GroupIDs> RegisteredGroupIDs;
+    std::unique_ptr<ACPOCollectFeatures::FeatureInfo> GlobalInfo;
+    std::unique_ptr<ACPOCollectFeatures> FeatureCollector;
+    std::string Prefix;
+    std::string Postfix;
+  };
+
+  void registerFeature(ACPOCollectFeatures::FeaturesInfo, std::string = "",
+                       std::string = "");
+  void registerFeature(ACPOCollectFeatures::Scopes,
+                       ACPOCollectFeatures::FeatureInfo, std::string = "",
+                       std::string = "");
+  void registerFeature(ACPOCollectFeatures::GroupIDs,
+                       ACPOCollectFeatures::FeatureInfo, std::string = "",
+                       std::string = "");
+  void resetRegisteredFeatures();
+
+protected:
+  // Collected features
+  std::vector<std::pair<std::string, std::string>> Features;
+  // NOTE: OutputFileName being empty (null) is treated as stdout
+  std::string OutputFileName;
+  std::vector<std::unique_ptr<FeatureCollectInfo>> FeatureCollectInfos;
+
+private:
+  // Stream for dumping training data
+  formatted_raw_ostream &Out;
+  StringMap<std::string> IRFileNames;
+};
+} // namespace llvm
+
+#endif // ENABLE_ACPO
+#endif // LLVM_ANALYSIS_MODELDATACOLLECTOR_H
diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index 80bec2d82e24..7fdb5db67c16 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -100,6 +100,7 @@ void initializeDomPrinterWrapperPassPass(PassRegistry &);
 void initializeDomViewerWrapperPassPass(PassRegistry &);
 void initializeDominanceFrontierWrapperPassPass(PassRegistry&);
 void initializeDominatorTreeWrapperPassPass(PassRegistry&);
+void initializeDumpCallsiteLegacyPass(PassRegistry &);
 void initializeDwarfEHPrepareLegacyPassPass(PassRegistry &);
 void initializeEarlyCSELegacyPassPass(PassRegistry&);
 void initializeEarlyCSEMemSSALegacyPassPass(PassRegistry&);
@@ -124,6 +125,7 @@ void initializeFixIrreduciblePass(PassRegistry &);
 void initializeFixupStatepointCallerSavedPass(PassRegistry&);
 void initializeFlattenCFGLegacyPassPass(PassRegistry &);
 void initializeFuncletLayoutPass(PassRegistry&);
+void initializeCallHeightAnalysisWrapperPass(PassRegistry &);
 void initializeGCMachineCodeAnalysisPass(PassRegistry&);
 void initializeGCModuleInfoPass(PassRegistry&);
 void initializeGVNLegacyPassPass(PassRegistry&);
@@ -132,6 +134,7 @@ void initializeGlobalsAAWrapperPassPass(PassRegistry&);
 void initializeGuardWideningLegacyPassPass(PassRegistry&);
 void initializeHardwareLoopsLegacyPass(PassRegistry&);
 void initializeMIRProfileLoaderPassPass(PassRegistry &);
+void initializeInlineAdvisorAnalysisWrapperPass(PassRegistry &);
 void initializeIRSimilarityIdentifierWrapperPassPass(PassRegistry&);
 void initializeIRTranslatorPass(PassRegistry&);
 void initializeIVUsersWrapperPassPass(PassRegistry&);
@@ -149,6 +152,11 @@ void initializeInterleavedLoadCombinePass(PassRegistry &);
 void initializeIntervalPartitionPass(PassRegistry&);
 void initializeJMCInstrumenterPass(PassRegistry&);
 void initializeKCFIPass(PassRegistry &);
+void initializeLegacyFAMPass(PassRegistry &);
+void initializeLegacyFunctionPropertiesAnalysisPass(PassRegistry &);
+void initializeLegacyInlinerPassPass(PassRegistry &);
+void initializeLegacyInlineSizeEstimatorAnalysisPass(PassRegistry &);
+void initializeLegacyModuleInlinerWrapperPassPass(PassRegistry &);
 void initializeLCSSAVerificationPassPass(PassRegistry&);
 void initializeLCSSAWrapperPassPass(PassRegistry&);
 void initializeLazyBlockFrequencyInfoPassPass(PassRegistry&);
diff --git a/llvm/lib/Analysis/ACPOCollectFeatures.cpp b/llvm/lib/Analysis/ACPOCollectFeatures.cpp
new file mode 100644
index 000000000000..f9de26483c76
--- /dev/null
+++ b/llvm/lib/Analysis/ACPOCollectFeatures.cpp
@@ -0,0 +1,1258 @@
+//===- ACPOCollectFeatures.cpp - ACPO Class for Feature Collection -------===//
+//
+// 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 file implements ACPOCollectFeatures class
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/ACPOCollectFeatures.h"
+#include "llvm/ADT/SCCIterator.h"
+// The ACPOFIModel.h currently contains only the cache system for
+// ACPOFIExtendedFeatures.
+#include "llvm/Analysis/ACPOFIModel.h"
+#include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/BlockFrequencyInfo.h"
+#include "llvm/Analysis/CallGraph.h"
+#include "llvm/Analysis/DumpFeature.h"
+#include "llvm/Analysis/FunctionPropertiesAnalysis.h"
+#include "llvm/Analysis/InlineAdvisor.h"
+#include "llvm/Analysis/InlineCost.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/IR/Dominators.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/Support/Debug.h"
+
+#define DEBUG_TYPE "ACPOCollectFeatures"
+
+namespace llvm {
+
+// Helper function that is used to calculate features and each function should
+// registered in the CalculateFeatureMap.
+static void calculateFPIRelated(ACPOCollectFeatures &ACF,
+                                const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateCallerBlockFreq(ACPOCollectFeatures &ACF,
+                         const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateCallSiteHeight(ACPOCollectFeatures &ACF,
+                        const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateConstantParam(ACPOCollectFeatures &ACF,
+                       const ACPOCollectFeatures::FeatureInfo &info);
+static void calculateCostEstimate(ACPOCollectFeatures &ACF,
+                                  const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateEdgeNodeCount(ACPOCollectFeatures &ACF,
+                       const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateHotColdCallSite(ACPOCollectFeatures &ACF,
+                         const ACPOCollectFeatures::FeatureInfo &info);
+static void calculateLoopLevel(ACPOCollectFeatures &ACF,
+                               const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateMandatoryKind(ACPOCollectFeatures &ACF,
+                       const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateMandatoryOnly(ACPOCollectFeatures &ACF,
+                       const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateInlineCostFeatures(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &info);
+static void calculateACPOFIExtendedFeaturesFeatures(
+    ACPOCollectFeatures &ACF, const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateIsIndirectCall(ACPOCollectFeatures &ACF,
+                        const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateIsInInnerLoop(ACPOCollectFeatures &ACF,
+                       const ACPOCollectFeatures::FeatureInfo &info);
+static void
+calculateIsMustTailCall(ACPOCollectFeatures &ACF,
+                        const ACPOCollectFeatures::FeatureInfo &info);
+static void calculateIsTailCall(ACPOCollectFeatures &ACF,
+                                const ACPOCollectFeatures::FeatureInfo &info);
+static void calculateOptCode(ACPOCollectFeatures &ACF,
+                             const ACPOCollectFeatures::FeatureInfo &info);
+
+// Register FeatureIdx -> Feature name
+//          FeatureIdx -> Scope, Scope -> FeatureIdx
+//          FeatureIdx -> Group, Group -> FeatureIdx
+//          FeatureIdx -> Calculating function
+#define REGISTER_NAME(INDEX_NAME, NAME)                                        \
+  { ACPOCollectFeatures::FeatureIndex::INDEX_NAME, NAME }
+const std::unordered_map<ACPOCollectFeatures::FeatureIndex, std::string>
+    ACPOCollectFeatures::FeatureIndexToName{
+        REGISTER_NAME(SROASavings, "sroa_savings"),
+        REGISTER_NAME(SROALosses, "sroa_losses"),
+        REGISTER_NAME(LoadElimination, "load_elimination"),
+        REGISTER_NAME(CallPenalty, "call_penalty"),
+        REGISTER_NAME(CallArgumentSetup, "call_argument_setup"),
+        REGISTER_NAME(LoadRelativeIntrinsic, "load_relative_intrinsic"),
+        REGISTER_NAME(LoweredCallArgSetup, "lowered_call_arg_setup"),
+        REGISTER_NAME(IndirectCallPenalty, "indirect_call_penalty"),
+        REGISTER_NAME(JumpTablePenalty, "jump_table_penalty"),
+        REGISTER_NAME(CaseClusterPenalty, "case_cluster_penalty"),
+        REGISTER_NAME(SwitchPenalty, "switch_penalty"),
+        REGISTER_NAME(UnsimplifiedCommonInstructions,
+                      "unsimplified_common_instructions"),
+        REGISTER_NAME(NumLoops, "num_loops"),
+        REGISTER_NAME(DeadBlocks, "dead_blocks"),
+        REGISTER_NAME(SimplifiedInstructions, "simplified_instructions"),
+        REGISTER_NAME(ConstantArgs, "constant_args"),
+        REGISTER_NAME(ConstantOffsetPtrArgs, "constant_offset_ptr_args"),
+        REGISTER_NAME(CallSiteCost, "callsite_cost"),
+        REGISTER_NAME(ColdCcPenalty, "cold_cc_penalty"),
+        REGISTER_NAME(LastCallToStaticBonus, "last_call_to_static_bonus"),
+        REGISTER_NAME(IsMultipleBlocks, "is_multiple_blocks"),
+        REGISTER_NAME(NestedInlines, "nested_inlines"),
+        REGISTER_NAME(NestedInlineCostEstimate, "nested_inline_cost_estimate"),
+        REGISTER_NAME(Threshold, "threshold"),
+        REGISTER_NAME(BasicBlockCount, "basic_block_count"),
+        REGISTER_NAME(BlocksReachedFromConditionalInstruction,
+                      "conditionally_executed_blocks"),
+        REGISTER_NAME(Uses, "users"),
+        REGISTER_NAME(EdgeCount, "edge_count"),
+        REGISTER_NAME(NodeCount, "node_count"),
+        REGISTER_NAME(ColdCallSite, "cold_callsite"),
+        REGISTER_NAME(HotCallSite, "hot_callsite"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesInitialSize, "InitialSize"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesBlocks, "Blocks"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesCalls, "Calls"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesIsLocal, "IsLocal"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesIsLinkOnceODR, "IsLinkOnceODR"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesIsLinkOnce, "IsLinkOnce"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesLoops, "Loops"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesMaxLoopDepth, "MaxLoopDepth"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesMaxDomTreeLevel, "MaxDomTreeLevel"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesPtrArgs, "PtrArgs"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesPtrCallee, "PtrCallee"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesCallReturnPtr, "CallReturnPtr"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesConditionalBranch,
+                      "ConditionalBranch"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesCBwithArg, "CBwithArg"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesCallerHeight, "CallerHeight"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesCallUsage, "CallUsage"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesIsRecursive, "IsRecursive"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesNumCallsiteInLoop,
+                      "NumCallsiteInLoop"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesNumOfCallUsesInLoop,
+                      "NumOfCallUsesInLoop"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesEntryBlockFreq, "EntryBlockFreq"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesMaxCallsiteBlockFreq,
+                      "MaxCallsiteBlockFreq"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesInstructionPerBlock,
+                      "InstructionPerBlock"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesSuccessorPerBlock,
+                      "SuccessorPerBlock"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesAvgVecInstr, "AvgVecInstr"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesAvgNestedLoopLevel,
+                      "AvgNestedLoopLevel"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesInstrPerLoop, "InstrPerLoop"),
+        REGISTER_NAME(ACPOFIExtendedFeaturesBlockWithMultipleSuccecorsPerLoop,
+                      "BlockWithMultipleSuccecorsPerLoop"),
+        REGISTER_NAME(CallerBlockFreq, "block_freq"),
+        REGISTER_NAME(CallSiteHeight, "callsite_height"),
+        REGISTER_NAME(ConstantParam, "nr_ctant_params"),
+        REGISTER_NAME(CostEstimate, "cost_estimate"),
+        REGISTER_NAME(LoopLevel, "loop_level"),
+        REGISTER_NAME(MandatoryKind, "mandatory_kind"),
+        REGISTER_NAME(MandatoryOnly, "mandatory_only"),
+        REGISTER_NAME(OptCode, "opt_code"),
+        REGISTER_NAME(IsIndirectCall, "is_indirect"),
+        REGISTER_NAME(IsInInnerLoop, "is_in_inner_loop"),
+        REGISTER_NAME(IsMustTailCall, "is_must_tail"),
+        REGISTER_NAME(IsTailCall, "is_tail"),
+        REGISTER_NAME(NumOfFeatures,"num_features"),
+    };
+#undef REGISTER_NAME
+
+#define REGISTER_SCOPE(INDEX_NAME, NAME)                                       \
+  {                                                                            \
+    ACPOCollectFeatures::FeatureIndex::INDEX_NAME,                             \
+        ACPOCollectFeatures::Scope::NAME                                       \
+  }
+const std::unordered_map<ACPOCollectFeatures::FeatureIndex,
+                         ACPOCollectFeatures::Scope>
+    ACPOCollectFeatures::FeatureIndexToScope{
+        REGISTER_SCOPE(SROASavings, CallSite),
+        REGISTER_SCOPE(SROALosses, CallSite),
+        REGISTER_SCOPE(LoadElimination, CallSite),
+        REGISTER_SCOPE(CallPenalty, CallSite),
+        REGISTER_SCOPE(CallArgumentSetup, CallSite),
+        REGISTER_SCOPE(LoadRelativeIntrinsic, CallSite),
+        REGISTER_SCOPE(LoweredCallArgSetup, CallSite),
+        REGISTER_SCOPE(IndirectCallPenalty, CallSite),
+        REGISTER_SCOPE(JumpTablePenalty, CallSite),
+        REGISTER_SCOPE(CaseClusterPenalty, CallSite),
+        REGISTER_SCOPE(SwitchPenalty, CallSite),
+        REGISTER_SCOPE(UnsimplifiedCommonInstructions, CallSite),
+        REGISTER_SCOPE(NumLoops, CallSite),
+        REGISTER_SCOPE(DeadBlocks, CallSite),
+        REGISTER_SCOPE(SimplifiedInstructions, CallSite),
+        REGISTER_SCOPE(ConstantArgs, CallSite),
+        REGISTER_SCOPE(ConstantOffsetPtrArgs, CallSite),
+        REGISTER_SCOPE(CallSiteCost, CallSite),
+        REGISTER_SCOPE(ColdCcPenalty, CallSite),
+        REGISTER_SCOPE(LastCallToStaticBonus, CallSite),
+        REGISTER_SCOPE(IsMultipleBlocks, CallSite),
+        REGISTER_SCOPE(NestedInlines, CallSite),
+        REGISTER_SCOPE(NestedInlineCostEstimate, CallSite),
+        REGISTER_SCOPE(Threshold, CallSite),
+        REGISTER_SCOPE(BasicBlockCount, Function),
+        REGISTER_SCOPE(BlocksReachedFromConditionalInstruction, Function),
+        REGISTER_SCOPE(Uses, Function),
+        REGISTER_SCOPE(EdgeCount, Module),
+        REGISTER_SCOPE(NodeCount, Module),
+        REGISTER_SCOPE(ColdCallSite, CallSite),
+        REGISTER_SCOPE(HotCallSite, CallSite),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesInitialSize, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesBlocks, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesCalls, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesIsLocal, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesIsLinkOnceODR, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesIsLinkOnce, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesLoops, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesMaxLoopDepth, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesMaxDomTreeLevel, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesPtrArgs, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesPtrCallee, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesCallReturnPtr, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesConditionalBranch, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesCBwithArg, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesCallerHeight, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesCallUsage, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesIsRecursive, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesNumCallsiteInLoop, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesNumOfCallUsesInLoop, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesEntryBlockFreq, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesMaxCallsiteBlockFreq, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesInstructionPerBlock, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesSuccessorPerBlock, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesAvgVecInstr, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesAvgNestedLoopLevel, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesInstrPerLoop, Function),
+        REGISTER_SCOPE(ACPOFIExtendedFeaturesBlockWithMultipleSuccecorsPerLoop,
+                       Function),
+        REGISTER_SCOPE(CallerBlockFreq, CallSite),
+        REGISTER_SCOPE(CallSiteHeight, CallSite),
+        REGISTER_SCOPE(ConstantParam, CallSite),
+        REGISTER_SCOPE(CostEstimate, CallSite),
+        REGISTER_SCOPE(LoopLevel, CallSite),
+        REGISTER_SCOPE(MandatoryKind, CallSite),
+        REGISTER_SCOPE(MandatoryOnly, CallSite),
+        REGISTER_SCOPE(OptCode, CallSite),
+        REGISTER_SCOPE(IsIndirectCall, CallSite),
+        REGISTER_SCOPE(IsInInnerLoop, CallSite),
+        REGISTER_SCOPE(IsMustTailCall, CallSite),
+        REGISTER_SCOPE(IsTailCall, CallSite),
+    };
+#undef REGISTER_SCOPE
+
+#define REGISTER_GROUP(INDEX_NAME, NAME)                                       \
+  {                                                                            \
+    ACPOCollectFeatures::FeatureIndex::INDEX_NAME,                             \
+        ACPOCollectFeatures::GroupID::NAME                                     \
+  }
+const std::unordered_map<ACPOCollectFeatures::FeatureIndex,
+                         ACPOCollectFeatures::GroupID>
+    ACPOCollectFeatures::FeatureIndexToGroup{
+        REGISTER_GROUP(SROASavings, InlineCostFeatureGroup),
+        REGISTER_GROUP(SROALosses, InlineCostFeatureGroup),
+        REGISTER_GROUP(LoadElimination, InlineCostFeatureGroup),
+        REGISTER_GROUP(CallPenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(CallArgumentSetup, InlineCostFeatureGroup),
+        REGISTER_GROUP(LoadRelativeIntrinsic, InlineCostFeatureGroup),
+        REGISTER_GROUP(LoweredCallArgSetup, InlineCostFeatureGroup),
+        REGISTER_GROUP(IndirectCallPenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(JumpTablePenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(CaseClusterPenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(SwitchPenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(UnsimplifiedCommonInstructions, InlineCostFeatureGroup),
+        REGISTER_GROUP(NumLoops, InlineCostFeatureGroup),
+        REGISTER_GROUP(DeadBlocks, InlineCostFeatureGroup),
+        REGISTER_GROUP(SimplifiedInstructions, InlineCostFeatureGroup),
+        REGISTER_GROUP(ConstantArgs, InlineCostFeatureGroup),
+        REGISTER_GROUP(ConstantOffsetPtrArgs, InlineCostFeatureGroup),
+        REGISTER_GROUP(CallSiteCost, InlineCostFeatureGroup),
+        REGISTER_GROUP(ColdCcPenalty, InlineCostFeatureGroup),
+        REGISTER_GROUP(LastCallToStaticBonus, InlineCostFeatureGroup),
+        REGISTER_GROUP(IsMultipleBlocks, InlineCostFeatureGroup),
+        REGISTER_GROUP(NestedInlines, InlineCostFeatureGroup),
+        REGISTER_GROUP(NestedInlineCostEstimate, InlineCostFeatureGroup),
+        REGISTER_GROUP(Threshold, InlineCostFeatureGroup),
+        REGISTER_GROUP(BasicBlockCount, FPIRelated),
+        REGISTER_GROUP(BlocksReachedFromConditionalInstruction, FPIRelated),
+        REGISTER_GROUP(Uses, FPIRelated),
+        REGISTER_GROUP(EdgeCount, EdgeNodeCount),
+        REGISTER_GROUP(NodeCount, EdgeNodeCount),
+        REGISTER_GROUP(ColdCallSite, HotColdCallSite),
+        REGISTER_GROUP(HotCallSite, HotColdCallSite),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesInitialSize,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesBlocks, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesCalls, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesIsLocal, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesIsLinkOnceODR,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesIsLinkOnce,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesLoops, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesMaxLoopDepth,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesMaxDomTreeLevel,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesPtrArgs, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesPtrCallee, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesCallReturnPtr,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesConditionalBranch,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesCBwithArg, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesCallerHeight,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesCallUsage, ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesIsRecursive,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesNumCallsiteInLoop,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesNumOfCallUsesInLoop,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesEntryBlockFreq,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesMaxCallsiteBlockFreq,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesInstructionPerBlock,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesSuccessorPerBlock,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesAvgVecInstr,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesAvgNestedLoopLevel,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesInstrPerLoop,
+                       ACPOFIExtendedFeatures),
+        REGISTER_GROUP(ACPOFIExtendedFeaturesBlockWithMultipleSuccecorsPerLoop,
+                       ACPOFIExtendedFeatures),
+    };
+#undef REGISTER_GROUP
+
+// Given a map that may not be one to one. Returns the inverse mapping.
+// EX: Input:  A -> 1, B -> 1
+//     Output: 1 -> A, 1 -> B
+template <class K, class V>
+static std::multimap<K, V> inverseMap(std::unordered_map<V, K> Map) {
+  std::multimap<K, V> InverseMap;
+  for (const auto &It : Map) {
+    InverseMap.insert(std::pair<K, V>(It.second, It.first));
+  }
+  return InverseMap;
+}
+
+const std::multimap<ACPOCollectFeatures::GroupID,
+                    ACPOCollectFeatures::FeatureIndex>
+    ACPOCollectFeatures::GroupToFeatureIndices{
+        inverseMap<ACPOCollectFeatures::GroupID,
+                   ACPOCollectFeatures::FeatureIndex>(FeatureIndexToGroup)};
+
+const std::multimap<ACPOCollectFeatures::Scope,
+                    ACPOCollectFeatures::FeatureIndex>
+    ACPOCollectFeatures::ScopeToFeatureIndices{
+        inverseMap<ACPOCollectFeatures::Scope,
+                   ACPOCollectFeatures::FeatureIndex>(FeatureIndexToScope)};
+
+#define REGISTER_FUNCTION(INDEX_NAME, NAME)                                    \
+  { ACPOCollectFeatures::FeatureIndex::INDEX_NAME, NAME }
+const std::unordered_map<ACPOCollectFeatures::FeatureIndex,
+                         ACPOCollectFeatures::CalculateFeatureFunction>
+    ACPOCollectFeatures::CalculateFeatureMap{
+        REGISTER_FUNCTION(SROASavings, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(SROALosses, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(LoadElimination, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(CallPenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(CallArgumentSetup, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(LoadRelativeIntrinsic, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(LoweredCallArgSetup, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(IndirectCallPenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(JumpTablePenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(CaseClusterPenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(SwitchPenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(UnsimplifiedCommonInstructions,
+                          calculateInlineCostFeatures),
+        REGISTER_FUNCTION(NumLoops, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(DeadBlocks, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(SimplifiedInstructions, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(ConstantArgs, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(ConstantOffsetPtrArgs, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(CallSiteCost, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(ColdCcPenalty, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(LastCallToStaticBonus, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(IsMultipleBlocks, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(NestedInlines, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(NestedInlineCostEstimate,
+                          calculateInlineCostFeatures),
+        REGISTER_FUNCTION(Threshold, calculateInlineCostFeatures),
+        REGISTER_FUNCTION(BasicBlockCount, calculateFPIRelated),
+        REGISTER_FUNCTION(BlocksReachedFromConditionalInstruction,
+                          calculateFPIRelated),
+        REGISTER_FUNCTION(Uses, calculateFPIRelated),
+        REGISTER_FUNCTION(EdgeCount, calculateEdgeNodeCount),
+        REGISTER_FUNCTION(NodeCount, calculateEdgeNodeCount),
+        REGISTER_FUNCTION(ColdCallSite, calculateHotColdCallSite),
+        REGISTER_FUNCTION(HotCallSite, calculateHotColdCallSite),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesInitialSize,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesBlocks,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesCalls,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesIsLocal,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesIsLinkOnceODR,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesIsLinkOnce,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesLoops,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesMaxLoopDepth,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesMaxDomTreeLevel,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesPtrArgs,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesPtrCallee,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesCallReturnPtr,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesConditionalBranch,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesCBwithArg,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesCallerHeight,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesCallUsage,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesIsRecursive,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesNumCallsiteInLoop,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesNumOfCallUsesInLoop,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesEntryBlockFreq,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesMaxCallsiteBlockFreq,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesInstructionPerBlock,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesSuccessorPerBlock,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesAvgVecInstr,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesAvgNestedLoopLevel,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(ACPOFIExtendedFeaturesInstrPerLoop,
+                          calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(
+            ACPOFIExtendedFeaturesBlockWithMultipleSuccecorsPerLoop,
+            calculateACPOFIExtendedFeaturesFeatures),
+        REGISTER_FUNCTION(CallerBlockFreq, calculateCallerBlockFreq),
+        REGISTER_FUNCTION(CallSiteHeight, calculateCallSiteHeight),
+        REGISTER_FUNCTION(ConstantParam, calculateConstantParam),
+        REGISTER_FUNCTION(CostEstimate, calculateCostEstimate),
+        REGISTER_FUNCTION(LoopLevel, calculateLoopLevel),
+        REGISTER_FUNCTION(MandatoryKind, calculateMandatoryKind),
+        REGISTER_FUNCTION(MandatoryOnly, calculateMandatoryOnly),
+        REGISTER_FUNCTION(OptCode, calculateOptCode),
+        REGISTER_FUNCTION(IsIndirectCall, calculateIsIndirectCall),
+        REGISTER_FUNCTION(IsInInnerLoop, calculateIsInInnerLoop),
+        REGISTER_FUNCTION(IsMustTailCall, calculateIsMustTailCall),
+        REGISTER_FUNCTION(IsTailCall, calculateIsTailCall),
+    };
+#undef REGISTER_FUNCTION
+
+std::map<const Function *, unsigned> ACPOCollectFeatures::FunctionLevels{};
+
+ACPOCollectFeatures::ACPOCollectFeatures() {}
+
+ACPOCollectFeatures::ACPOCollectFeatures(
+    ACPOCollectFeatures::FeatureInfo GlobalInfo)
+    : GlobalFeatureInfo(GlobalInfo) {
+  assert(GlobalFeatureInfo.Idx == FeatureIndex::NumOfFeatures &&
+         "When setting glboal FeatureInfo the Idx should always be "
+         "NumOfFeatures");
+}
+
+ACPOCollectFeatures::~ACPOCollectFeatures() {}
+
+void ACPOCollectFeatures::setFeatureValue(ACPOCollectFeatures::FeatureIndex Idx,
+                                          std::string Val) {
+  FeatureToValue[Idx] = Val;
+}
+
+void ACPOCollectFeatures::setFeatureInfo(
+    ACPOCollectFeatures::FeatureIndex Idx,
+    ACPOCollectFeatures::FeatureInfo Info) {
+  assert(
+      (Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+       Info.Idx == Idx || getFeatureGroup(Info.Idx) == getFeatureGroup(Idx)) &&
+      "When setting FeatureToInfo map the key and value pair should both refer "
+      "to the same Feature or the FeatureInfo.Idx should be NumOfFeatures.");
+  FeatureToInfo[Idx] = Info;
+}
+
+void ACPOCollectFeatures::setFeatureValueAndInfo(
+    ACPOCollectFeatures::FeatureIndex Idx,
+    ACPOCollectFeatures::FeatureInfo Info, std::string Val) {
+  setFeatureValue(Idx, Val);
+  setFeatureInfo(Idx, Info);
+}
+
+void ACPOCollectFeatures::setGlobalFeatureInfo(
+    ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == FeatureIndex::NumOfFeatures &&
+         "When setting glboal FeatureInfo the Idx should always be "
+         "NumOfFeatures");
+  GlobalFeatureInfo = Info;
+}
+
+std::string
+ACPOCollectFeatures::getFeature(ACPOCollectFeatures::FeatureIndex Idx) const {
+  assert(registeredFeature(Idx) && "Feature not registered");
+  return FeatureToValue.find(Idx)->second;
+}
+
+std::string
+ACPOCollectFeatures::getFeatureName(ACPOCollectFeatures::FeatureIndex Idx) {
+  return FeatureIndexToName.find(Idx)->second;
+}
+
+ACPOCollectFeatures::GroupID
+ACPOCollectFeatures::getFeatureGroup(ACPOCollectFeatures::FeatureIndex Idx) {
+  return FeatureIndexToGroup.find(Idx)->second;
+}
+
+ACPOCollectFeatures::Scope
+ACPOCollectFeatures::getFeatureScope(ACPOCollectFeatures::FeatureIndex Idx) {
+  return FeatureIndexToScope.find(Idx)->second;
+}
+
+std::set<ACPOCollectFeatures::FeatureIndex>
+ACPOCollectFeatures::getGroupFeatures(ACPOCollectFeatures::GroupID Group) {
+  std::set<ACPOCollectFeatures::FeatureIndex> FeatureIndices;
+  auto Range = GroupToFeatureIndices.equal_range(Group);
+  for (auto It = Range.first; It != Range.second; ++It) {
+    FeatureIndices.insert(It->second);
+  }
+  return FeatureIndices;
+}
+
+std::set<ACPOCollectFeatures::FeatureIndex>
+ACPOCollectFeatures::getScopeFeatures(ACPOCollectFeatures::Scope S) {
+  std::set<ACPOCollectFeatures::FeatureIndex> FeatureIndices;
+  auto Range = ScopeToFeatureIndices.equal_range(S);
+  for (auto It = Range.first; It != Range.second; ++It) {
+    FeatureIndices.insert(It->second);
+  }
+  return FeatureIndices;
+}
+
+bool ACPOCollectFeatures::containsFeature(
+    ACPOCollectFeatures::FeatureIndex Idx) {
+  return FeatureToValue.count(Idx) > 0;
+}
+
+bool ACPOCollectFeatures::containsFeature(
+    ACPOCollectFeatures::GroupID GroupID) {
+  for (auto FeatureIdx : getGroupFeatures(GroupID)) {
+    if (!containsFeature(FeatureIdx))
+      return false;
+  }
+  return true;
+}
+
+void ACPOCollectFeatures::clearFeatureValueMap() { FeatureToValue.clear(); }
+
+bool ACPOCollectFeatures::registeredFeature(
+    ACPOCollectFeatures::FeatureIndex Idx) const {
+  return FeatureToValue.find(Idx) != FeatureToValue.end();
+}
+
+void calculateFPIRelated(ACPOCollectFeatures &ACF,
+                         const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::BasicBlockCount);
+
+  auto *FAM = Info.Managers.FAM;
+  auto *F = Info.SI.F;
+
+  assert(F && FAM && "Function or FAM is nullptr");
+
+  auto &FPI = FAM->getResult<FunctionPropertiesAnalysis>(*F);
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::BasicBlockCount,
+                             Info, std::to_string(FPI.BasicBlockCount));
+  ACF.setFeatureValueAndInfo(
+      ACPOCollectFeatures::FeatureIndex::
+          BlocksReachedFromConditionalInstruction,
+      Info, std::to_string(FPI.BlocksReachedFromConditionalInstruction));
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::Uses, Info,
+                             std::to_string(FPI.Uses));
+}
+
+void calculateCallerBlockFreq(ACPOCollectFeatures &ACF,
+                              const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::CallerBlockFreq);
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallSite or FAM is nullptr");
+
+  Function *F = CB->getCaller();
+  BasicBlock *BB = CB->getParent();
+  BlockFrequencyInfo &BFI = FAM->getResult<BlockFrequencyAnalysis>(*F);
+
+  uint64_t CallerBlockFreq = BFI.getBlockFreq(BB).getFrequency();
+  // The model uses signed 64-bit thus we need to take care of int overflow.
+  if (CallerBlockFreq >= std::numeric_limits<int64_t>::max()) {
+    CallerBlockFreq = std::numeric_limits<int64_t>::max() - 1;
+  }
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::CallerBlockFreq,
+                             Info, std::to_string(CallerBlockFreq));
+}
+
+void calculateCallSiteHeight(ACPOCollectFeatures &ACF,
+                             const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::CallSiteHeight);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::CallSiteHeight))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *IA = Info.OI.IA;
+
+  assert(CB && IA && "CallSite or IA is nullptr");
+
+  if (IA) {
+    ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::CallSiteHeight,
+                              Info, std::to_string(IA->getCallSiteHeight(CB)));
+    return;
+  }
+  LLVM_DEBUG(dbgs() << "IA was nullptr & callsite height is not set!" << "\n");
+}
+
+void calculateConstantParam(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::ConstantParam);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::ConstantParam))
+    return;
+
+  auto *CB = Info.SI.CB;
+  assert(CB && "CallSite is nullptr");
+
+  size_t NrCtantParams = 0;
+  for (auto I = CB->arg_begin(), E = CB->arg_end(); I != E; ++I) {
+    NrCtantParams += (isa<Constant>(*I));
+  }
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::ConstantParam,
+                             Info, std::to_string(NrCtantParams));
+}
+
+void calculateCostEstimate(ACPOCollectFeatures &ACF,
+                           const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::CostEstimate);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::CostEstimate))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallBase or FAM is nullptr");
+
+  auto &Callee = *CB->getCalledFunction();
+  auto &TIR = FAM->getResult<TargetIRAnalysis>(Callee);
+
+  auto GetAssumptionCache = [&](Function &F) -> AssumptionCache & {
+    return FAM->getResult<AssumptionAnalysis>(F);
+  };
+
+  int CostEstimate = 0;
+  auto IsCallSiteInlinable =
+      llvm::getInliningCostEstimate(*CB, TIR, GetAssumptionCache);
+  if (IsCallSiteInlinable)
+    CostEstimate = *IsCallSiteInlinable;
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::CostEstimate,
+                             Info, std::to_string(CostEstimate));
+}
+
+int64_t getLocalCalls(Function &F, FunctionAnalysisManager &FAM) {
+  return FAM.getResult<FunctionPropertiesAnalysis>(F)
+      .DirectCallsToDefinedFunctions;
+}
+
+void calculateEdgeNodeCount(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         ACPOCollectFeatures::getFeatureGroup(Info.Idx) ==
+             ACPOCollectFeatures::GroupID::EdgeNodeCount);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::GroupID::EdgeNodeCount))
+    return;
+
+  auto *M = Info.SI.M;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(M && FAM && "Module or FAM is nullptr");
+
+  int NodeCount = 0;
+  int EdgeCount = 0;
+  for (auto &F : *M)
+    if (!F.isDeclaration()) {
+      ++NodeCount;
+      EdgeCount += getLocalCalls(F, *FAM);
+    }
+
+  std::string EdgeCountStr = std::to_string(EdgeCount);
+  std::string NodeCountStr = std::to_string(NodeCount);
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::EdgeCount, Info,
+                             EdgeCountStr);
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::NodeCount, Info,
+                             NodeCountStr);
+}
+
+void calculateHotColdCallSite(ACPOCollectFeatures &ACF,
+                              const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         ACPOCollectFeatures::getFeatureGroup(Info.Idx) ==
+             ACPOCollectFeatures::GroupID::HotColdCallSite);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::GroupID::HotColdCallSite))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "Module or FAM is nullptr");
+
+  auto &Caller = *CB->getCaller();
+  auto GetBFI = [&](Function &F) -> BlockFrequencyInfo & {
+    return FAM->getResult<BlockFrequencyAnalysis>(F);
+  };
+
+  BlockFrequencyInfo &CallerBFI = GetBFI(Caller);
+  const BranchProbability ColdProb(2, 100);
+  auto *CallSiteBB = CB->getParent();
+  auto CallSiteFreq = CallerBFI.getBlockFreq(CallSiteBB);
+  auto CallerEntryFreq =
+      CallerBFI.getBlockFreq(&(CB->getCaller()->getEntryBlock()));
+  bool ColdCallSite = CallSiteFreq < CallerEntryFreq * ColdProb;
+  auto CallerEntryFreqHot = CallerBFI.getEntryFreq();
+  bool HotCallSite = (CallSiteFreq.getFrequency() >= CallerEntryFreqHot * 60);
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::ColdCallSite,
+                             Info, std::to_string(ColdCallSite));
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::HotCallSite,
+                             Info, std::to_string(HotCallSite));
+}
+
+void calculateLoopLevel(ACPOCollectFeatures &ACF,
+                        const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::LoopLevel);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::LoopLevel))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallBase or FAM is nullptr");
+
+  Function *F = CB->getCaller();
+  BasicBlock *BB = CB->getParent();
+  LoopInfo &LI = FAM->getResult<LoopAnalysis>(*F);
+
+  std::string OptCode = std::to_string(CB->getOpcode());
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::LoopLevel, Info,
+                             std::to_string(LI.getLoopDepth(BB)));
+}
+
+InlineAdvisor::MandatoryInliningKind
+ACPOCollectFeatures::getMandatoryKind(CallBase &CB,
+                                      FunctionAnalysisManager &FAM,
+                                      OptimizationRemarkEmitter &ORE) {
+  return InlineAdvisor::getMandatoryKind(CB, FAM, ORE);
+}
+
+void calculateMandatoryKind(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::MandatoryKind);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::MandatoryKind))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallBase or FAM is nullptr");
+
+  auto &Caller = *CB->getCaller();
+  auto &ORE = FAM->getResult<OptimizationRemarkEmitterAnalysis>(Caller);
+  auto MandatoryKind = ACPOCollectFeatures::getMandatoryKind(*CB, *FAM, ORE);
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::MandatoryKind,
+                             Info, std::to_string((int)MandatoryKind));
+}
+
+void calculateMandatoryOnly(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::MandatoryOnly);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::MandatoryOnly))
+    return;
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::MandatoryOnly,
+                             Info, std::to_string((int)Info.OI.MandatoryOnly));
+}
+
+void calculateOptCode(ACPOCollectFeatures &ACF,
+                      const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::OptCode);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::OptCode))
+    return;
+
+  auto *CB = Info.SI.CB;
+
+  assert(CB && "CallBase is nullptr");
+
+  std::string OptCode = std::to_string(CB->getOpcode());
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::OptCode, Info,
+                             OptCode);
+}
+
+void calculateInlineCostFeatures(ACPOCollectFeatures &ACF,
+                                 const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         (ACPOCollectFeatures::getFeatureGroup(Info.Idx) ==
+          ACPOCollectFeatures::GroupID::InlineCostFeatureGroup));
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::GroupID::InlineCostFeatureGroup))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallBase or FAM is nullptr");
+
+  auto &Callee = *CB->getCalledFunction();
+  auto &TIR = FAM->getResult<TargetIRAnalysis>(Callee);
+
+  auto GetAssumptionCache = [&](Function &F) -> AssumptionCache & {
+    return FAM->getResult<AssumptionAnalysis>(F);
+  };
+
+  const auto CostFeaturesOpt =
+      getInliningCostFeatures(*CB, TIR, GetAssumptionCache);
+
+  for (auto Idx =
+           ACPOCollectFeatures::FeatureIndex::InlineCostFeatureGroupBegin + 1;
+       Idx != ACPOCollectFeatures::FeatureIndex::InlineCostFeatureGroupEnd;
+       ++Idx) {
+    size_t TmpIdx =
+        static_cast<size_t>(Idx) -
+        static_cast<size_t>(
+            ACPOCollectFeatures::FeatureIndex::InlineCostFeatureGroupBegin) -
+        1;
+    ACF.setFeatureValueAndInfo(
+        Idx, Info,
+        std::to_string(CostFeaturesOpt ? CostFeaturesOpt.value()[TmpIdx] : 0));
+  }
+}
+
+static void
+checkValidFFCache(Function &F,
+                  struct ACPOFIExtendedFeatures::FunctionFeatures &FF,
+                  DominatorTree &Tree, TargetTransformInfo &TTI, LoopInfo &LI,
+                  bool &ValidSize, bool &ValidLoop, bool &ValidTree) {
+  std::optional<size_t> SizeCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize);
+  auto TTIAnalysisCache = ACPOFIModel::getTTICachedAnalysis(&F);
+  if (SizeCache && TTIAnalysisCache == &TTI) {
+    ValidSize = true;
+  }
+
+  std::optional<size_t> MaxDomTreeLevelCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel);
+  auto DomCache = ACPOFIModel::getDomCachedAnalysis(&F);
+  if (MaxDomTreeLevelCache && DomCache == &Tree) {
+    ValidTree = true;
+  }
+
+  std::optional<size_t> LoopNumCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::Loops);
+  auto LIAnalysisCache = ACPOFIModel::getLICachedAnalysis(&F);
+  if (LoopNumCache && LIAnalysisCache == &LI) {
+    ValidLoop = true;
+  }
+}
+
+static void getCachedFF(Function &F,
+                        struct ACPOFIExtendedFeatures::FunctionFeatures &FF,
+                        DominatorTree &Tree, TargetTransformInfo &TTI,
+                        LoopInfo &LI) {
+  std::optional<size_t> SizeCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize);
+  auto TTIAnalysisCache = ACPOFIModel::getTTICachedAnalysis(&F);
+  if (SizeCache && TTIAnalysisCache == &TTI) {
+    FF[ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize] =
+        SizeCache.value();
+  }
+
+  std::optional<size_t> MaxDomTreeLevelCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel);
+  auto DomCache = ACPOFIModel::getDomCachedAnalysis(&F);
+  if (MaxDomTreeLevelCache && DomCache == &Tree) {
+    FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel] =
+        MaxDomTreeLevelCache.value();
+  }
+
+  std::optional<size_t> LoopNumCache = ACPOFIModel::getCachedSize(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::Loops);
+  auto LIAnalysisCache = ACPOFIModel::getLICachedAnalysis(&F);
+  if (LoopNumCache && LIAnalysisCache == &LI) {
+    FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Loops] = LoopNumCache.value();
+    FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth] =
+        ACPOFIModel::getCachedSize(
+            &F, ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth)
+            .value();
+    if (LoopNumCache.value() != 0) {
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop] =
+          ACPOFIModel::getCachedFloat(
+              &F, ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop)
+              .value();
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+             BlockWithMultipleSuccecorsPerLoop] =
+          ACPOFIModel::getCachedFloat(
+              &F, ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                      BlockWithMultipleSuccecorsPerLoop)
+              .value();
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel] =
+          ACPOFIModel::getCachedFloat(
+              &F, ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                      AvgNestedLoopLevel)
+              .value();
+    }
+  }
+}
+
+static void updateCachedFF(Function &F,
+                           struct ACPOFIExtendedFeatures::FunctionFeatures &FF,
+                           DominatorTree &Tree, TargetTransformInfo &TTI,
+                           LoopInfo &LI) {
+  ACPOFIModel::insertSizeCache(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize,
+      FF[ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize]);
+  ACPOFIModel::insertAnalysisCache(&F, &TTI);
+  ACPOFIModel::insertSizeCache(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel,
+      FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel]);
+  ACPOFIModel::insertAnalysisCache(&F, &Tree);
+  ACPOFIModel::insertSizeCache(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::Loops,
+      FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Loops]);
+  ACPOFIModel::insertSizeCache(
+      &F, ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth,
+      FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth]);
+  ACPOFIModel::insertFloatCache(
+      &F, ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop,
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop]);
+  ACPOFIModel::insertFloatCache(
+      &F,
+      ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+          BlockWithMultipleSuccecorsPerLoop,
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+             BlockWithMultipleSuccecorsPerLoop]);
+  ACPOFIModel::insertFloatCache(
+      &F, ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel,
+      FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel]);
+  ACPOFIModel::insertAnalysisCache(&F, &LI);
+}
+
+void calculateACPOFIExtendedFeaturesFeatures(
+    ACPOCollectFeatures &ACF, const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         ACPOCollectFeatures::getFeatureGroup(Info.Idx) ==
+             ACPOCollectFeatures::GroupID::ACPOFIExtendedFeatures);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::GroupID::ACPOFIExtendedFeatures))
+    return;
+
+  auto F = Info.SI.F;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(F && FAM && "F or FAM is nullptr");
+
+  struct ACPOFIExtendedFeatures::FunctionFeatures FF;
+  auto &DomTree = FAM->getResult<DominatorTreeAnalysis>(*F);
+  auto &TTI = FAM->getResult<TargetIRAnalysis>(*F);
+  auto &LI = FAM->getResult<LoopAnalysis>(*F);
+  bool ValidSize = false;
+  bool ValidLoop = false;
+  bool ValidTree = false;
+  checkValidFFCache(*F, FF, DomTree, TTI, LI, ValidSize, ValidLoop, ValidTree);
+  FF = ACPOFIExtendedFeatures::getFunctionFeatures(
+      *F, DomTree, TTI, LI, FAM, ValidSize, ValidLoop, ValidTree);
+  getCachedFF(*F, FF, DomTree, TTI, LI);
+  updateCachedFF(*F, FF, DomTree, TTI, LI);
+
+  for (auto Idx = ACPOCollectFeatures::FeatureIndex::
+                      ACPOFIExtendedFeaturesNamedFeatureBegin +
+                  1;
+       Idx !=
+       ACPOCollectFeatures::FeatureIndex::ACPOFIExtendedFeaturesNamedFeatureEnd;
+       ++Idx) {
+    size_t TmpIdx =
+        static_cast<size_t>(Idx) -
+        static_cast<size_t>(ACPOCollectFeatures::FeatureIndex::
+                                ACPOFIExtendedFeaturesNamedFeatureBegin) -
+        1;
+    ACF.setFeatureValueAndInfo(Idx, Info,
+                               std::to_string(FF.NamedFeatures[TmpIdx]));
+  }
+  for (auto Idx = ACPOCollectFeatures::FeatureIndex::
+                      ACPOFIExtendedFeaturesFloatFeatureBegin +
+                  1;
+       Idx !=
+       ACPOCollectFeatures::FeatureIndex::ACPOFIExtendedFeaturesFloatFeatureEnd;
+       ++Idx) {
+    size_t TmpIdx =
+        static_cast<size_t>(Idx) -
+        static_cast<size_t>(ACPOCollectFeatures::FeatureIndex::
+                                ACPOFIExtendedFeaturesFloatFeatureBegin) -
+        1;
+    ACF.setFeatureValueAndInfo(Idx, Info,
+                               std::to_string(FF.NamedFloatFeatures[TmpIdx]));
+  }
+}
+
+void calculateIsIndirectCall(ACPOCollectFeatures &ACF,
+                             const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::IsIndirectCall);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::IsIndirectCall))
+    return;
+
+  auto *CB = Info.SI.CB;
+
+  assert(CB && "CallBase is nullptr");
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::IsIndirectCall,
+                             Info, std::to_string(CB->isIndirectCall()));
+}
+
+void calculateIsInInnerLoop(ACPOCollectFeatures &ACF,
+                            const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::IsInInnerLoop);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::IsInInnerLoop))
+    return;
+
+  auto *CB = Info.SI.CB;
+  auto *FAM = Info.Managers.FAM;
+
+  assert(CB && FAM && "CallBase or FAM is nullptr");
+
+  auto &Caller = *CB->getCaller();
+  auto &CallerLI = FAM->getResult<LoopAnalysis>(Caller);
+
+  // Get loop for CB's BB. And check whether the loop is an inner most loop.
+  bool CallSiteInInnerLoop = false;
+  for (auto &L : CallerLI) {
+    if (L->isInnermost() && L->contains(CB))
+      CallSiteInInnerLoop = true;
+  }
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::IsInInnerLoop,
+                             Info, std::to_string(CallSiteInInnerLoop));
+}
+
+void calculateIsMustTailCall(ACPOCollectFeatures &ACF,
+                             const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::IsMustTailCall);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::IsMustTailCall))
+    return;
+
+  auto *CB = Info.SI.CB;
+
+  assert(CB && "CallBase is nullptr");
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::IsMustTailCall,
+                             Info, std::to_string(CB->isMustTailCall()));
+}
+
+void calculateIsTailCall(ACPOCollectFeatures &ACF,
+                         const ACPOCollectFeatures::FeatureInfo &Info) {
+  assert(Info.Idx == ACPOCollectFeatures::FeatureIndex::NumOfFeatures ||
+         Info.Idx == ACPOCollectFeatures::FeatureIndex::IsTailCall);
+
+  // Check if we already calculated the values.
+  if (ACF.containsFeature(ACPOCollectFeatures::FeatureIndex::IsTailCall))
+    return;
+
+  auto *CB = Info.SI.CB;
+
+  assert(CB && "CallBase is nullptr");
+
+  ACF.setFeatureValueAndInfo(ACPOCollectFeatures::FeatureIndex::IsTailCall,
+                             Info, std::to_string(CB->isTailCall()));
+}
+
+ACPOCollectFeatures::FeatureValueMap ACPOCollectFeatures::getFeaturesPair(
+    ACPOCollectFeatures::FeaturesInfo FeatureInfoVec) {
+  clearFeatureValueMap();
+  for (auto &FeatureInfo : FeatureInfoVec) {
+    auto It = CalculateFeatureMap.find(FeatureInfo.Idx);
+    if (It == CalculateFeatureMap.end()) {
+      assert("Could not find the corresponding function to calculate feature");
+    }
+    auto CalculateFunction = It->second;
+    CalculateFunction(*this, FeatureInfo);
+    LLVM_DEBUG(dbgs() << "ACPO Feature " << getFeatureName(FeatureInfo.Idx)
+                                         << ": " << FeatureToValue[FeatureInfo.Idx] << "\n");
+  }
+
+  return FeatureToValue;
+}
+
+ACPOCollectFeatures::FeatureValueMap
+ACPOCollectFeatures::getFeaturesPair(ACPOCollectFeatures::Scopes ScopeVec) {
+  clearFeatureValueMap();
+  for (auto Scope : ScopeVec) {
+    for (auto FeatureIdx : getScopeFeatures(Scope)) {
+      auto It = CalculateFeatureMap.find(FeatureIdx);
+      if (It == CalculateFeatureMap.end()) {
+        assert(
+            "Could not find the corresponding function to calculate feature");
+      }
+      auto CalculateFunction = It->second;
+      CalculateFunction(*this, GlobalFeatureInfo);
+      LLVM_DEBUG(dbgs() << "ACPO Feature " << getFeatureName(FeatureIdx)
+                                           << ": " << FeatureToValue[FeatureIdx] << "\n");
+    }
+  }
+
+  return FeatureToValue;
+}
+
+ACPOCollectFeatures::FeatureValueMap
+ACPOCollectFeatures::getFeaturesPair(ACPOCollectFeatures::GroupIDs GroupIDVec) {
+  clearFeatureValueMap();
+  for (auto GroupID : GroupIDVec) {
+    for (auto FeatureIdx : getGroupFeatures(GroupID)) {
+      auto It = CalculateFeatureMap.find(FeatureIdx);
+      if (It == CalculateFeatureMap.end()) {
+        assert(
+            "Could not find the corresponding function to calculate feature");
+      }
+      auto CalculateFunction = It->second;
+      CalculateFunction(*this, GlobalFeatureInfo);
+      LLVM_DEBUG(dbgs() << "ACPO Feature " << getFeatureName(FeatureIdx)
+                                           << ": " << FeatureToValue[FeatureIdx] << "\n");
+    }
+  }
+
+  return FeatureToValue;
+}
+
+ACPOCollectFeatures::FeatureValueMap
+ACPOCollectFeatures::getFeaturesPair(ACPOCollectFeatures::FeatureIndex Beg,
+                                     ACPOCollectFeatures::FeatureIndex End) {
+  assert(Beg <= End);
+  for (auto Idx = Beg; Idx != End; ++Idx) {
+    auto It = CalculateFeatureMap.find(Idx);
+    if (It == CalculateFeatureMap.end()) {
+      assert("Could not find the corresponding function to calculate feature");
+    }
+    auto CalculateFunction = It->second;
+    CalculateFunction(*this, GlobalFeatureInfo);
+  }
+
+  return FeatureToValue;
+}
+
+void ACPOCollectFeatures::clearFunctionLevel() { FunctionLevels.clear(); }
+
+void ACPOCollectFeatures::insertFunctionLevel(const Function *F, unsigned FL) {
+  FunctionLevels[F] = FL;
+}
+
+std::optional<unsigned>
+ACPOCollectFeatures::getFunctionLevel(const Function *F) {
+  auto It = FunctionLevels.find(F);
+  if (It == FunctionLevels.end()) {
+    return std::nullopt;
+  } else {
+    return It->second;
+  }
+}
+
+ACPOCollectFeatures::FeatureIndex operator+(ACPOCollectFeatures::FeatureIndex N,
+                                            int Counter) {
+  return static_cast<ACPOCollectFeatures::FeatureIndex>((int)N + Counter);
+}
+
+ACPOCollectFeatures::FeatureIndex operator-(ACPOCollectFeatures::FeatureIndex N,
+                                            int Counter) {
+  return static_cast<ACPOCollectFeatures::FeatureIndex>((int)N - Counter);
+}
+
+ACPOCollectFeatures::FeatureIndex &
+operator++(ACPOCollectFeatures::FeatureIndex &N) {
+  return N = static_cast<ACPOCollectFeatures::FeatureIndex>((int)N + 1);
+}
+
+ACPOCollectFeatures::FeatureIndex
+operator++(ACPOCollectFeatures::FeatureIndex &N, int) {
+  ACPOCollectFeatures::FeatureIndex Res = N;
+  ++N;
+  return Res;
+}
+
+} // namespace llvm
diff --git a/llvm/lib/Analysis/ACPOMLInterface.cpp b/llvm/lib/Analysis/ACPOMLInterface.cpp
new file mode 100644
index 000000000000..271dcfe7d851
--- /dev/null
+++ b/llvm/lib/Analysis/ACPOMLInterface.cpp
@@ -0,0 +1,1405 @@
+//===- ACPOMLInterface.cpp - AI-Enabled Continuous Program Optimization ---===//
+//
+// 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 file implements an interface to the ML framework.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/ACPOMLInterface.h"
+#include "llvm/Analysis/ACPOModelRunner.h"
+#include "llvm/Analysis/FIModelRunner.h"
+#include "llvm/Analysis/TensorSpec.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <ctime>
+#include <fstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+#include <Windows.h>
+#else
+#include <unistd.h>
+#endif
+
+using namespace llvm;
+
+#define DEBUG_TYPE "acpo"
+
+#define ACPO_ENV_VAR_DIR "ACPO_DIR"
+#define ACPO_ML_PYTHON_INTERFACE_PY "MLInterface.py"
+#define ACPO_PYTHON_EXECUTABLE "python"
+#define ACPO_PIPE_PREFIX "ACPO_Pipe"
+
+#define RESPONSE_MODEL_LOADED "Model loaded"
+#define RESPONSE_ALREADY_IN_DICT "already in dict"
+#define RESPONSE_FEATURE_SET "Feature set"
+#define RESPONSE_FEATURES_INITIALIZED "Features initialized"
+#define RESPONSE_FEATURES_SET "Features set"
+#define RESPONSE_COMPLETED "Completed"
+#define RESPONSE_ACTIVE "Active"
+#define RESPONSE_ERROR "ERROR"
+
+// Static variables
+
+static std::shared_ptr<ACPOMLInterface> PersistentMLIF = nullptr;
+
+// Class definitions
+
+bool Model::registerFeature(std::string FeatureName, uint64_t FeatureID,
+                            int Index) {
+  auto Find1 = NameToID.find(FeatureName);
+  if (Find1 != NameToID.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerFeature: Feature " << FeatureName
+                      << " already exists\n");
+    return false;
+  }
+  NameToID.insert(std::make_pair(FeatureName, FeatureID));
+  IDToName.insert(std::make_pair(FeatureID, FeatureName));
+  auto Find2 = IDToIndex.find(FeatureID);
+  if (Find2 != IDToIndex.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerFeature: Feature with ID "
+                      << FeatureID << " already exists\n");
+    return false;
+  }
+  IDToIndex.insert(std::make_pair(FeatureID, Index));
+  return true;
+}
+
+bool Model::registerInput(std::string InputName, std::string InputType) {
+  auto Find = InputMap.find(InputName);
+  if (Find != InputMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerInput: Input " << InputName
+                      << " already exists\n");
+    return false;
+  }
+  InputMap.insert(std::make_pair(InputName, InputType));
+  return true;
+}
+
+bool Model::registerOutput(std::string OutputName, std::string OutputType) {
+  auto Find = OutputMap.find(OutputName);
+  if (Find != OutputMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerOutput: Output " << OutputName
+                      << " already exists\n");
+    return false;
+  }
+  OutputMap.insert(std::make_pair(OutputName, OutputType));
+  return true;
+}
+
+int Model::getIndex(uint64_t FeatureID) const {
+  auto Find = IDToIndex.find(FeatureID);
+  assert(Find != IDToIndex.end());
+  return Find->second;
+}
+
+int Model::getIndex(std::string FeatureName) const {
+  auto Find = NameToID.find(FeatureName);
+  assert(Find != NameToID.end());
+  uint64_t ID = Find->second;
+  return getIndex(ID);
+}
+
+std::string Model::getName(uint64_t FeatureID) const {
+  auto Find = IDToName.find(FeatureID);
+  assert(Find != IDToName.end());
+  return Find->second;
+}
+
+bool Model::checkOutputExists(std::string OutputName) const {
+  return (OutputMap.find(OutputName) != OutputMap.end());
+}
+
+std::string Model::getInputType(std::string InputName) const {
+  auto Find = InputMap.find(InputName);
+  assert(Find != InputMap.end());
+  return Find->second;
+}
+
+std::string Model::getOutputType(std::string OutputName) const {
+  auto Find = OutputMap.find(OutputName);
+  assert(Find != OutputMap.end());
+  return Find->second;
+}
+
+ACPOMLPythonInterface::ACPOMLPythonInterface() : NextID{0} {
+  std::optional<std::string> Env = llvm::sys::Process::GetEnv(ACPO_ENV_VAR_DIR);
+  if (!Env || *Env == "") {
+    std::optional<std::string> LLVMDIROpt =
+        llvm::sys::Process::GetEnv("LLVM_DIR");
+    if (LLVMDIROpt) {
+      Env = *LLVMDIROpt + "/acpo/";
+    } else {
+      return;
+    }
+  }
+
+  int32_t PID = (int32_t) llvm::sys::Process::getProcessId();
+  std::string ExecPython = "/usr/bin/python3";
+  std::string
+      PythonScript = *Env + "/" + std::string(ACPO_ML_PYTHON_INTERFACE_PY);
+  std::string PIDStr = std::to_string(PID);
+  std::string TimeStr = std::to_string(time(nullptr));
+  std::string NameOut =
+      *Env + "/" + ACPO_PIPE_PREFIX + "_CMD_" + PIDStr + "_" + TimeStr;
+  std::string NameIn =
+      *Env + "/" + ACPO_PIPE_PREFIX + "_RESP_" + PIDStr + "_" + TimeStr;
+  StringRef Args[] = { ExecPython, PythonScript, NameOut, NameIn };
+
+  // Start a process and don't wait for it to finish. We want it running in
+  // tandem.
+  std::string ErrMsg;
+  SubProcess =
+      sys::ExecuteNoWait(ExecPython, Args, std::nullopt, {}, 0, &ErrMsg);
+  if (!SubProcess.Pid) {
+    // Print out error message if the process fails to start.
+    LLVM_DEBUG(dbgs() << ErrMsg << "\n");
+    return;
+  }
+  // Yield to Python Process to set up pipes.
+  const int PythonProcessStartupLatency = 100;
+  usleep(PythonProcessStartupLatency);
+
+  // Now link to named pipes created by the process we just started. Note that
+  // because the creation of this file as a pipe was done elsewhere, the
+  // interface here is simple.
+
+  // First check that the response pipe has been created by attempting to open a
+  // file for reading. If this is not successful, then sleep for 100us to allow
+  // the ML interface the time to create named pipes and open the response pipe
+  // for writing. Once that is done, the fopen call will pass here.
+
+  // FIXME: Support library provides robust and portable APIs for opening files
+  // and creating input/output streams. Use them instead of calling libc
+  // functions.
+  PipeIn = fopen(NameIn.c_str(), "r");
+  if (PipeIn == nullptr) {
+    do {
+      usleep(100);
+      PipeIn = fopen(NameIn.c_str(), "r");
+    } while (PipeIn == nullptr);
+  }
+
+  // Once the response FIFO is created, then open the command FIFO for writing.
+  // This will complete the handshake with the MLInterface in Python.
+  PipeOut = fopen(NameOut.c_str(), "w");
+  // Now open named pipes to the new process.
+  setInitialized(true);
+}
+
+ACPOMLPythonInterface::~ACPOMLPythonInterface() {
+  if (SubProcess.Pid)
+    closeMLInterface();
+  if (PipeIn)
+    fclose(PipeIn);
+  if (PipeOut)
+    fclose(PipeOut);
+  if (SubProcess.Pid) {
+    // Wait for the MLInterface 3 seconds and kill it.
+    sys::Wait(SubProcess, 3) ;
+    SubProcess = sys::ProcessInfo{};
+  }
+  setInitialized(false);
+}
+
+uint64_t ACPOMLPythonInterface::assignID() {
+  NextID++;
+  return NextID - 1;
+}
+
+bool ACPOMLPythonInterface::loadModel(std::string ModelSpecFile) {
+  sendCommand("LoadModel " + ModelSpecFile);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  if (Tokens[0] != RESPONSE_MODEL_LOADED) {
+    return false;
+  }
+  if (Tokens[1] == RESPONSE_ALREADY_IN_DICT) {
+    LLVM_DEBUG(dbgs() << "loadModel: the model specified in " << ModelSpecFile
+                      << " has already been loaded\n");
+    return true;
+  }
+  std::string ModelName = Tokens[1];
+  int NumFeatures = std::stoi(Tokens[2]);
+  LLVM_DEBUG(dbgs() << "Registering features: " << NumFeatures << "\n");
+  registerModel(ModelName, NumFeatures);
+  auto ModelPtr = ModelMap.find(ModelName)->second;
+  std::string FeatureName = "";
+  for (int I = 0; I < NumFeatures; I++) {
+    FeatureName = Tokens[I + 3];
+    if (!registerFeature(ModelName, FeatureName, I)) {
+      return false;
+    }
+  }
+  int OutputStart = 3 + NumFeatures;
+  int NumOutputs = std::stoi(Tokens[OutputStart]);
+  ModelPtr->setNumOutputs(NumOutputs);
+  OutputStart++;
+  std::string OutputName;
+  std::string OutputType;
+  for (int I = 0; I < NumOutputs; I++) {
+    std::istringstream IS(Tokens[OutputStart + I]);
+    IS >> OutputName >> OutputType;
+    if (!registerOutput(ModelName, OutputName, OutputType)) {
+      return false;
+    }
+  }
+  std::string Signature = Tokens[OutputStart + NumOutputs];
+  ModelPtr->setSignature(Signature);
+  return true;
+}
+
+bool ACPOMLPythonInterface::registerModel(std::string ModelName,
+                                          int NumFeatures) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find != ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "registerModel: Model " << ModelName
+                      << " already exists\n");
+    return false;
+  }
+  std::shared_ptr<Model> NewModel = std::make_shared<Model>(NumFeatures);
+  ModelMap.insert(std::make_pair(ModelName, NewModel));
+  return true;
+}
+
+bool ACPOMLPythonInterface::registerModel(std::string ModelName,
+                                          int NumFeatures, int NumOutputs) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find != ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "registerModel: Model " << ModelName
+                      << " already exists\n");
+    return false;
+  }
+  std::shared_ptr<Model> NewModel =
+      std::make_shared<Model>(NumFeatures, NumOutputs);
+  ModelMap.insert(std::make_pair(ModelName, NewModel));
+  return true;
+}
+
+bool ACPOMLPythonInterface::registerFeature(std::string ModelName,
+                                            std::string FeatureName,
+                                            int Index) {
+  auto Find = ModelMap.find(ModelName);
+  assert(Find != ModelMap.end());
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  uint64_t ID = assignID();
+  return Find->second->registerFeature(FeatureName, ID, Index);
+}
+
+bool ACPOMLPythonInterface::registerOutput(std::string ModelName,
+                                           std::string OutputName,
+                                           std::string OutputType) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerOutput: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  return Find->second->registerOutput(OutputName, OutputType);
+}
+
+int ACPOMLPythonInterface::getNumLoadedModels() { return ModelMap.size(); }
+
+bool ACPOMLPythonInterface::defineInputIR(std::string Filename) {
+  return false;
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             uint64_t FeatureID,
+                                             int FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             uint64_t FeatureID,
+                                             int64_t FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             uint64_t FeatureID,
+                                             double FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             uint64_t FeatureID,
+                                             float FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             uint64_t FeatureID,
+                                             bool FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::string Command = "SetCustomFeature " + std::to_string(Index) + " ";
+  Command += FeatureValue ? "1" : "0";
+  sendCommand(Command);
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             std::string FeatureName,
+                                             int FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             std::string FeatureName,
+                                             int64_t FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             std::string FeatureName,
+                                             double FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             std::string FeatureName,
+                                             float FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  sendCommand("SetCustomFeature " + std::to_string(Index) + " " +
+              std::to_string(FeatureValue));
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeature(std::string ModelName,
+                                             std::string FeatureName,
+                                             bool FeatureValue) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::string Command = "SetCustomFeature " + std::to_string(Index) + " ";
+  Command += FeatureValue ? "1" : "0";
+  sendCommand(Command);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  return (Response.find(RESPONSE_FEATURE_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::initializeFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid features\n");
+    return false;
+  }
+  CurrentlyActiveModel = ModelName;
+  std::string Command = "InitializeFeatures " + ModelName;
+  for (const auto &Feature : FeatureValues) {
+    uint64_t FeatureID = Feature.first;
+    std::string FeatureValue = Feature.second;
+    int Index = Find->second->getIndex(FeatureID);
+    Command += " " + std::to_string(Index) + " " + FeatureValue;
+  }
+  sendCommand(Command);
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURES_INITIALIZED) == 0);
+}
+
+bool ACPOMLPythonInterface::initializeFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<std::string, std::string>> &FeatureValues) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid features\n");
+    return false;
+  }
+  CurrentlyActiveModel = ModelName;
+  std::string Command = "InitializeFeatures " + ModelName;
+  for (const auto &Feature : FeatureValues) {
+    std::string FeatureName = Feature.first;
+    std::string FeatureValue = Feature.second;
+    int Index = Find->second->getIndex(FeatureName);
+    Command += " " + std::to_string(Index) + " " + FeatureValue;
+  }
+  sendCommand(Command);
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURES_INITIALIZED) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Model " << ModelName
+                      << " has not been loaded or is not active\n");
+    return false;
+  }
+  auto Find = ModelMap.find(ModelName);
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid features\n");
+    return false;
+  }
+  std::string Command = "SetCustomFeatures";
+  for (const auto &Feature : FeatureValues) {
+    uint64_t FeatureID = Feature.first;
+    std::string FeatureValue = Feature.second;
+    int Index = Find->second->getIndex(FeatureID);
+    Command += " " + std::to_string(Index) + " " + FeatureValue;
+  }
+  sendCommand(Command);
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURES_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::setCustomFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<std::string, std::string>> &FeatureValues) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Model " << ModelName
+                      << " has not been loaded or is not active\n");
+    return false;
+  }
+  auto Find = ModelMap.find(ModelName);
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid features\n");
+    return false;
+  }
+  std::string Command = "SetCustomFeatures";
+  for (const auto &Feature : FeatureValues) {
+    std::string FeatureName = Feature.first;
+    std::string FeatureValue = Feature.second;
+    int Index = Find->second->getIndex(FeatureName);
+    Command += " " + std::to_string(Index) + " " + FeatureValue;
+  }
+  sendCommand(Command);
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_FEATURES_SET) == 0);
+}
+
+bool ACPOMLPythonInterface::runModel(std::string ModelName) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in runModel: Model " << ModelName
+                      << " is not active\n");
+    return false;
+  }
+  sendCommand("RunModel");
+  std::string Response = getResponse();
+  return (Response.find(RESPONSE_COMPLETED) == 0);
+}
+
+std::string ACPOMLPythonInterface::getOutputType(std::string ModelName,
+                                                 std::string OutputName) {
+  auto Find = ModelMap.find(ModelName);
+  assert(Find != ModelMap.end());
+  return Find->second->getOutputType(OutputName);
+}
+
+int ACPOMLPythonInterface::getModelResultI(std::string OutputName) {
+  auto Find = ModelMap.find(CurrentlyActiveModel);
+  assert(Find->second->checkOutputExists(OutputName));
+  sendCommand("GetModelOutput " + OutputName);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  assert(Tokens.size() == 3);
+  assert(Tokens[0] == OutputName);
+  int Result = std::stoi(Tokens[2]);
+  return Result;
+}
+
+int64_t ACPOMLPythonInterface::getModelResultI64(std::string OutputName) {
+  auto Find = ModelMap.find(CurrentlyActiveModel);
+  assert(Find->second->checkOutputExists(OutputName));
+  sendCommand("GetModelOutput " + OutputName);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  assert(Tokens.size() == 3);
+  assert(Tokens[0] == OutputName);
+  int64_t Result = std::stol(Tokens[2]);
+  return Result;
+}
+
+float ACPOMLPythonInterface::getModelResultF(std::string OutputName) {
+  auto Find = ModelMap.find(CurrentlyActiveModel);
+  assert(Find->second->checkOutputExists(OutputName));
+  sendCommand("GetModelOutput " + OutputName);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  assert(Tokens.size() == 3);
+  assert(Tokens[0] == OutputName);
+  float Result = std::stof(Tokens[2]);
+  return Result;
+}
+
+double ACPOMLPythonInterface::getModelResultD(std::string OutputName) {
+  auto Find = ModelMap.find(CurrentlyActiveModel);
+  assert(Find->second->checkOutputExists(OutputName));
+  sendCommand("GetModelOutput " + OutputName);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  assert(Tokens.size() == 3);
+  assert(Tokens[0] == OutputName);
+  double Result = std::stod(Tokens[2]);
+  return Result;
+}
+
+bool ACPOMLPythonInterface::getModelResultB(std::string OutputName) {
+  auto Find = ModelMap.find(CurrentlyActiveModel);
+  assert(Find->second->checkOutputExists(OutputName));
+  sendCommand("GetModelOutput " + OutputName);
+  std::string Response = getResponse();
+  std::vector<std::string> Tokens = tokenize(Response);
+  assert(Tokens.size() == 3);
+  assert(Tokens[0] == OutputName);
+  return (Tokens[2] == "1");
+}
+
+int ACPOMLPythonInterface::getStatus() {
+  sendCommand("GetStatus");
+  std::string Response = getResponse();
+  return Response.find(RESPONSE_ACTIVE) == 0;
+}
+
+bool ACPOMLPythonInterface::releaseModel(std::string ModelName) {
+  sendCommand("ReleaseModel " + ModelName);
+  std::string Response = getResponse();
+  ModelMap.erase(ModelName);
+  CurrentlyActiveModel = "";
+  return true;
+}
+
+bool ACPOMLPythonInterface::closeMLInterface() {
+  sendCommand("CloseMLInterface");
+  std::string Response = getResponse();
+  return true;
+}
+
+void ACPOMLPythonInterface::sendCommand(const std::string &Command) {
+  fprintf(PipeOut,"%s\n", Command.c_str());
+  fflush(PipeOut);
+  usleep(1);
+}
+
+void ACPOMLPythonInterface::sendCommand(
+    const std::vector<std::string> &Features) {
+  for (auto I = Features.begin(); I != Features.end(); I++) {
+    fprintf(PipeOut,"%s\n", I->c_str());
+    fflush(PipeOut);
+    usleep(1);
+  }
+}
+
+std::string ACPOMLPythonInterface::getResponse() {
+  std::string Response = "";
+  char Letter = getc(PipeIn);
+  while (Letter != '\n') {
+    if (feof(PipeIn))
+      assert(false && "ACPO pipeline is closed unexpectively.");
+
+    Response += Letter;
+    Letter = getc(PipeIn);
+  }
+  Response += '\n';
+  if (Response.substr(0, 5) == RESPONSE_ERROR) {
+    LLVM_DEBUG(dbgs() << Response);
+    assert(false && "MLInterface reutrned error");
+  }
+  return Response;
+}
+
+std::vector<std::string>
+ACPOMLPythonInterface::tokenize(const std::string &Line) {
+  std::vector<std::string> Result;
+  std::string Temp = Line;
+  auto Loc = Temp.find(",");
+  while (Loc != std::string::npos) {
+    std::string Sub = Temp.substr(0, Loc);
+    Result.push_back(Sub);
+    Temp = Temp.substr(Loc + 1);
+    Loc = Temp.find(",");
+  }
+  if (Temp.length() > 0)
+    Result.push_back(Temp);
+
+  return Result;
+}
+
+std::shared_ptr<ACPOMLInterface> llvm::createPersistentPythonMLIF() {
+  if (PersistentMLIF == nullptr) {
+    PersistentMLIF = std::make_shared<ACPOMLPythonInterface>();
+
+    if (!PersistentMLIF->isInitialized())
+      PersistentMLIF = nullptr;
+  }
+  return PersistentMLIF;
+}
+
+ACPOMLCPPInterface::ACPOMLCPPInterface() { setInitialized(true); }
+
+ACPOMLCPPInterface::~ACPOMLCPPInterface() {}
+
+uint64_t ACPOMLCPPInterface::assignID() {
+  NextID++;
+  return NextID - 1;
+}
+
+bool ACPOMLCPPInterface::loadModel(std::string ModelSpecFile) {
+  std::string ModelName = readModelParam(ModelSpecFile, "ModelName");
+  // Check if the model is already in the dictionary
+  if (RunnerMap.find(ModelName) != RunnerMap.end()) {
+    LLVM_DEBUG(dbgs() << "loadModel: the compiled model '" << ModelName
+                      << "' has already been loaded\n");
+    return true;
+  }
+  std::vector<std::pair<std::string, std::string>> Features{};
+  readFeatures(ModelSpecFile, Features);
+  std::vector<std::pair<std::string, std::string>> Outputs{};
+  readOutputs(ModelSpecFile, Outputs);
+
+  LLVM_DEBUG(llvm::dbgs() << "Loading compiled model with name " << ModelName
+                          << "\n");
+
+  auto CreatorFunctionIterator = CreateModelRunnerMap.find(ModelName);
+  if (CreatorFunctionIterator == CreateModelRunnerMap.end()) {
+    LLVM_DEBUG(llvm::dbgs()
+               << ("Could not find compiled model class for model '" +
+                   ModelName + "'\n"));
+    return false;
+  }
+
+  auto CreatorFunction = CreatorFunctionIterator->second;
+
+  std::string OutputKey = readModelParam(ModelSpecFile, "OutputKey");
+  auto ModelRunner = CreatorFunction(Features, OutputKey);
+
+  registerModel(ModelName, Features.size());
+  RunnerMap.insert(std::make_pair(ModelName, std::move(ModelRunner)));
+  auto ModelPtr = ModelMap.find(ModelName)->second;
+  for (size_t I = 0; I < Features.size(); I++) {
+    if (!registerFeature(ModelName, Features[I].first, I)) {
+      return false;
+    }
+    if (!ModelPtr->registerInput(Features[I].first, Features[I].second)) {
+      return false;
+    }
+  }
+
+  ModelPtr->setNumOutputs(Outputs.size());
+  for (size_t I = 0; I < Outputs.size(); I++) {
+    if (!registerOutput(ModelName, Outputs[I].first, Outputs[I].second)) {
+      return false;
+    }
+  }
+
+  LLVM_DEBUG(llvm::dbgs() << "Model " << ModelName
+                          << " was successfully loaded\n");
+
+  // We do not need to set signature here because it is already given to make
+  // the precompiled model
+  return true;
+}
+
+bool ACPOMLCPPInterface::registerModel(std::string ModelName, int NumFeatures) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find != ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "registerModel: Model " << ModelName
+                      << " already exists\n");
+    return false;
+  }
+  std::shared_ptr<Model> NewModel = std::make_shared<Model>(NumFeatures);
+  ModelMap.insert(std::make_pair(ModelName, NewModel));
+  return true;
+}
+
+bool ACPOMLCPPInterface::registerModel(std::string ModelName, int NumFeatures,
+                                       int NumOutputs) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find != ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "registerModel: Model " << ModelName
+                      << " already exists\n");
+    return false;
+  }
+  std::shared_ptr<Model> NewModel =
+      std::make_shared<Model>(NumFeatures, NumOutputs);
+  ModelMap.insert(std::make_pair(ModelName, NewModel));
+  return true;
+}
+
+bool ACPOMLCPPInterface::registerFeature(std::string ModelName,
+                                         std::string FeatureName, int Index) {
+  auto Find = ModelMap.find(ModelName);
+  assert(Find != ModelMap.end());
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  uint64_t ID = assignID();
+  return Find->second->registerFeature(FeatureName, ID, Index);
+}
+
+bool ACPOMLCPPInterface::registerOutput(std::string ModelName,
+                                        std::string OutputName,
+                                        std::string OutputType) {
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in registerOutput: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  return Find->second->registerOutput(OutputName, OutputType);
+}
+
+int ACPOMLCPPInterface::getNumLoadedModels() { return ModelMap.size(); }
+
+bool ACPOMLCPPInterface::defineInputIR(std::string Filename) { return false; }
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          uint64_t FeatureID,
+                                          int FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type int in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          uint64_t FeatureID,
+                                          int64_t FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type double in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          uint64_t FeatureID,
+                                          double FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type double in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          uint64_t FeatureID,
+                                          float FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type float in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          uint64_t FeatureID,
+                                          bool FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type bool in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureID);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          std::string FeatureName,
+                                          int FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type int in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          std::string FeatureName,
+                                          int64_t FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type int64 in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          std::string FeatureName,
+                                          double FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type double in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          std::string FeatureName,
+                                          float FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type float in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::setCustomFeature(std::string ModelName,
+                                          std::string FeatureName,
+                                          bool FeatureValue) {
+  LLVM_DEBUG(
+      dbgs()
+      << "ACPOMLCPPInterface: setting custom feature of type bool in model "
+      << ModelName << "\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeature: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  int Index = Find->second->getIndex(FeatureName);
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(ModelName)->second;
+  return Runner->setCustomFeature(Index, FeatureValue);
+}
+
+bool ACPOMLCPPInterface::initializeFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) {
+  LLVM_DEBUG(dbgs() << "Initializing features for model " << ModelName
+                    << " using feature IDs\n");
+  auto Find = ModelMap.find(ModelName);
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid features\n");
+    return false;
+  }
+  CurrentlyActiveModel = ModelName;
+  for (const auto &Feature : FeatureValues) {
+    uint64_t FeatureID = Feature.first;
+    std::string FeatureValue = Feature.second;
+
+    std::string FeatureType =
+        getInputType(ModelName, Find->second->getName(FeatureID));
+    if (FeatureType == "int64") {
+      int64_t Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "int32") {
+      int32_t Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "int") {
+      int Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "float64") {
+      double Value = std::stod(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "float32") {
+      float Value = std::stof(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else {
+      LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid feature type "
+                        << FeatureType << "\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ACPOMLCPPInterface::initializeFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<std::string, std::string>> &FeatureValues) {
+  auto Find = ModelMap.find(ModelName);
+  LLVM_DEBUG(dbgs() << "Initializing features for model " << ModelName
+                    << " using feature names\n");
+  if (Find == ModelMap.end()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Model " << ModelName
+                      << " has not been loaded\n");
+    return false;
+  }
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid features\n");
+    return false;
+  }
+  CurrentlyActiveModel = ModelName;
+  for (const auto &Feature : FeatureValues) {
+    std::string FeatureName = Feature.first;
+    std::string FeatureValue = Feature.second;
+
+    std::string FeatureType = getInputType(ModelName, FeatureName);
+    if (FeatureType == "int64") {
+      int64_t Value = std::stol(FeatureValue);
+      setCustomFeature(ModelName, FeatureName, Value);
+    } else if (FeatureType == "int32") {
+      int32_t Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureName, Value);
+    } else if (FeatureType == "int") {
+      int Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureName, Value);
+    } else if (FeatureType == "float64") {
+      double Value = std::stod(FeatureValue);
+      setCustomFeature(ModelName, FeatureName, Value);
+    } else if (FeatureType == "float32") {
+      float Value = std::stof(FeatureValue);
+      setCustomFeature(ModelName, FeatureName, Value);
+    } else {
+      LLVM_DEBUG(dbgs() << "ERROR in initializeFeatures: Invalid feature type "
+                        << FeatureType << "\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ACPOMLCPPInterface::setCustomFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<uint64_t, std::string>> &FeatureValues) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Model " << ModelName
+                      << " has not been loaded or is not active\n");
+    return false;
+  }
+  auto Find = ModelMap.find(ModelName);
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid features\n");
+    return false;
+  }
+  std::string Command = "SetCustomFeatures";
+  for (const auto &Feature : FeatureValues) {
+    uint64_t FeatureID = Feature.first;
+    std::string FeatureValue = Feature.second;
+
+    std::string FeatureType =
+        getInputType(ModelName, Find->second->getName(FeatureID));
+    if (FeatureType == "int64") {
+      int64_t Value = std::stol(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "int32") {
+      int32_t Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "int") {
+      int Value = std::stoi(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "float64") {
+      double Value = std::stod(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else if (FeatureType == "float32") {
+      float Value = std::stof(FeatureValue);
+      setCustomFeature(ModelName, FeatureID, Value);
+    } else {
+      LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid feature type "
+                        << FeatureType << "\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ACPOMLCPPInterface::setCustomFeatures(
+    std::string ModelName,
+    const std::vector<std::pair<std::string, std::string>> &FeatureValues) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Model " << ModelName
+                      << " has not been loaded or is not active\n");
+    return false;
+  }
+  auto Find = ModelMap.find(ModelName);
+  if (FeatureValues.size() > Find->second->getNumFeatures()) {
+    LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid features\n");
+    return false;
+  }
+  std::string Command = "SetCustomFeatures";
+  for (const auto &Feature : FeatureValues) {
+    std::string FeatureName = Feature.first;
+    std::string FeatureValueStr = Feature.second;
+
+    std::string FeatureType = getInputType(ModelName, FeatureName);
+    if (FeatureType == "int64") {
+      int64_t FeatureValue = std::stoi(FeatureValueStr);
+      setCustomFeature(ModelName, FeatureName, FeatureValue);
+    } else if (FeatureType == "int32") {
+      int32_t FeatureValue = std::stoi(FeatureValueStr);
+      setCustomFeature(ModelName, FeatureName, FeatureValue);
+    } else if (FeatureType == "int") {
+      int FeatureValue = std::stoi(FeatureValueStr);
+      setCustomFeature(ModelName, FeatureName, FeatureValue);
+    } else if (FeatureType == "float64") {
+      double FeatureValue = std::stod(FeatureValueStr);
+      setCustomFeature(ModelName, FeatureName, FeatureValue);
+    } else if (FeatureType == "float32") {
+      float FeatureValue = std::stof(FeatureValueStr);
+      setCustomFeature(ModelName, FeatureName, FeatureValue);
+    } else {
+      LLVM_DEBUG(dbgs() << "ERROR in setCustomFeatures: Invalid feature type "
+                        << FeatureType << "\n");
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ACPOMLCPPInterface::runModel(std::string ModelName) {
+  if (ModelName != CurrentlyActiveModel) {
+    LLVM_DEBUG(dbgs() << "ERROR in runModel: Model " << ModelName
+                      << " is not active\n");
+    return false;
+  }
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->runModel();
+}
+
+std::string ACPOMLCPPInterface::getInputType(std::string ModelName,
+                                             std::string InputName) {
+  auto Find = ModelMap.find(ModelName);
+  assert(Find != ModelMap.end());
+  return Find->second->getInputType(InputName);
+}
+
+std::string ACPOMLCPPInterface::getOutputType(std::string ModelName,
+                                              std::string OutputName) {
+  auto Find = ModelMap.find(ModelName);
+  assert(Find != ModelMap.end());
+  return Find->second->getOutputType(OutputName);
+}
+
+int ACPOMLCPPInterface::getModelResultI(std::string OutputName) {
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->getModelResultI(OutputName);
+}
+
+int64_t ACPOMLCPPInterface::getModelResultI64(std::string OutputName) {
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->getModelResultI64(OutputName);
+}
+
+float ACPOMLCPPInterface::getModelResultF(std::string OutputName) {
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->getModelResultF(OutputName);
+}
+
+double ACPOMLCPPInterface::getModelResultD(std::string OutputName) {
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->getModelResultD(OutputName);
+}
+
+bool ACPOMLCPPInterface::getModelResultB(std::string OutputName) {
+  std::shared_ptr<llvm::ACPOModelRunner> Runner =
+      RunnerMap.find(CurrentlyActiveModel)->second;
+  return Runner->getModelResultB(OutputName);
+}
+
+int ACPOMLCPPInterface::getStatus() { return 1; }
+
+bool ACPOMLCPPInterface::releaseModel(std::string ModelName) {
+  ModelMap.erase(ModelName);
+  RunnerMap.erase(ModelName);
+  CurrentlyActiveModel = "";
+  return true;
+}
+
+bool ACPOMLCPPInterface::closeMLInterface() { return true; }
+
+std::string ACPOMLCPPInterface::readModelParam(std::string FilePath,
+                                               std::string Param) {
+  std::optional<std::string> Env = llvm::sys::Process::GetEnv(ACPO_ENV_VAR_DIR);
+  if (!Env || *Env == "") {
+    std::optional<std::string> LLVMDIROpt =
+        llvm::sys::Process::GetEnv("LLVM_DIR");
+    if (LLVMDIROpt) {
+      Env = *LLVMDIROpt + "/acpo/";
+    } else {
+      return "";
+    }
+  }
+
+  FilePath = *Env + "/" + FilePath;
+
+  std::ifstream FileStream{FilePath};
+
+  std::string Line;
+  while (std::getline(FileStream, Line)) {
+    if (Line.rfind(Param, 0) == 0) {
+      return Line.substr(Param.size() + 1);
+    }
+  }
+  return "";
+}
+
+void ACPOMLCPPInterface::readFeatures(
+    std::string FilePath,
+    std::vector<std::pair<std::string, std::string>> &Features) {
+  std::string Line = readModelParam(FilePath, "Features");
+  while (!Line.empty()) {
+    // This reads the features, assuming each feature is written as
+    // {feature_name, feature_type}
+    size_t LeftBracket = Line.find("{");
+    size_t Comma = Line.find(",", LeftBracket);
+    size_t Space = Line.find(" ", Comma);
+    size_t RightBracket = Line.find("}", Space);
+    if (LeftBracket == Line.size() || Comma == Line.size() ||
+        Space == Line.size() || RightBracket == Line.size()) {
+      break;
+    }
+    std::string Feature = Line.substr(LeftBracket + 1, Comma - LeftBracket - 1);
+    std::string Type = Line.substr(Space + 1, RightBracket - Space - 1);
+
+    Features.emplace_back(std::make_pair(Feature, Type));
+    int oldLength = Line.size();
+    Line = Line.substr(RightBracket + 1);
+    int newLength = Line.size();
+    if (oldLength == newLength)
+      break;
+  }
+}
+
+void ACPOMLCPPInterface::readOutputs(
+    std::string FilePath,
+    std::vector<std::pair<std::string, std::string>> &Outputs) {
+  std::string Line = readModelParam(FilePath, "Outputs");
+  while (!Line.empty()) {
+    // This reads the features, assuming each feature is written as
+    // {feature_name, feature_type}
+    size_t LeftBracket = Line.find("{");
+    size_t Comma = Line.find(",", LeftBracket);
+    size_t Space = Line.find(" ", Comma);
+    size_t RightBracket = Line.find("}", Space);
+    if (LeftBracket == Line.size() || Comma == Line.size() ||
+        Space == Line.size() || RightBracket == Line.size()) {
+      break;
+    }
+    std::string Output = Line.substr(LeftBracket + 1, Comma - LeftBracket - 1);
+    std::string Type = Line.substr(Space + 1, RightBracket - Space - 1);
+
+    Outputs.emplace_back(std::make_pair(Output, Type));
+    int oldLength = Line.size();
+    Line = Line.substr(RightBracket + 1);
+    int newLength = Line.size();
+    if (oldLength == newLength)
+      break;
+  }
+}
+
+std::shared_ptr<ACPOMLInterface> llvm::createPersistentCompiledMLIF() {
+  if (PersistentMLIF == nullptr) {
+    PersistentMLIF = std::make_shared<ACPOMLCPPInterface>();
+    if (!PersistentMLIF->isInitialized())
+      PersistentMLIF = nullptr;
+  }
+  return PersistentMLIF;
+}
+
+#ifdef LLVM_HAVE_TF_AOT_FICOMPILEDMODEL
+std::unique_ptr<ACPOModelRunner>
+createFI(std::vector<std::pair<std::string, std::string>> Inputs,
+         StringRef Decision) {
+  // Context does not ever seem to be used in the model runner,
+  // so for now just create an empty context object
+  LLVMContext Ctx;
+  return std::make_unique<FIModelRunner>(Ctx, Inputs, Decision);
+}
+#endif
+
+// Generate map using ifdefs for now, in the future we could have this
+// automatically populate using macros
+const std::unordered_map<std::string,
+                         ACPOMLCPPInterface::CreateModelRunnerFunction>
+    ACPOMLCPPInterface::CreateModelRunnerMap = {
+#ifdef LLVM_HAVE_TF_AOT_FICOMPILEDMODEL
+        {"FI", createFI},
+#endif
+};
diff --git a/llvm/lib/Analysis/ACPOModel.cpp b/llvm/lib/Analysis/ACPOModel.cpp
new file mode 100644
index 000000000000..2d0dae733943
--- /dev/null
+++ b/llvm/lib/Analysis/ACPOModel.cpp
@@ -0,0 +1,63 @@
+//===- ACPOModel.cpp - AI-Enabled Continuous Program Optimization ---------===//
+//
+// 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 file implements the interface between ACPO and ML-guided optimizations.
+// It delegates decision making to inference with a pre-trained model.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/ACPOModel.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/Support/Debug.h"
+#include <memory>
+
+using namespace llvm;
+
+#define DEBUG_TYPE "acpo"
+
+ACPOAdvice::ACPOAdvice(std::unique_ptr<ACPOAdvice> &ResultFormat) {
+  assert(ResultFormat != nullptr);
+  for (auto &Entry : ResultFormat->getFieldMap()) {
+    reserveField(Entry.first, Entry.second.T);
+  }
+}
+
+void ACPOModel::prepareModelInput() {}
+
+bool ACPOModel::runModel(std::unique_ptr<ACPOAdvice> &Result) { return true; }
+
+void ACPOModel::addRequiredResultField(std::string name, Type::TypeID &ID) {
+  ResultFormat->reserveField(name, ID);
+}
+
+std::unique_ptr<ACPOAdvice> ACPOModel::getAdvice() {
+  if (ShouldUseML)
+    return getAdviceML();
+  else
+    return getAdviceNoML();
+}
+
+std::unique_ptr<ACPOAdvice> ACPOModel::getAdviceML() {
+  // This needs to be filled with a mechanism to invoke a model selected
+  // using the ModelRunner.
+  sendCustomFeatures();
+  prepareModelInput();
+  std::unique_ptr<ACPOAdvice> Result =
+      std::make_unique<ACPOAdvice>(ResultFormat);
+
+  if (runModel(Result))
+    return Result;
+  else
+    return nullptr;
+}
+
+void ACPOModel::addFeature(int64_t ID, Constant *Val) {
+  assert(CustomFeatureMap.find(ID) == CustomFeatureMap.end());
+  CustomFeatureMap[ID] = Val;
+}
diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt
index 9c6a70f0221f..961b5037dd48 100644
--- a/llvm/lib/Analysis/CMakeLists.txt
+++ b/llvm/lib/Analysis/CMakeLists.txt
@@ -4,6 +4,30 @@ if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
 
   set(LLVM_INLINER_MODEL_CURRENT_URL "<UNSPECIFIED>" CACHE STRING "URL to download the LLVM inliner model")
 
+  if (ACPO_AOT)
+    foreach (model_name model_path model_signature IN ZIP_LISTS LLVM_ACPO_MODEL_NAMES LLVM_ACPO_MODEL_PATHS LLVM_ACPO_MODEL_SIGNATURES)
+      set(fname ${model_name}CompiledModel)
+      string(TOUPPER ${fname} fname_allcaps)
+      if (LLVM_ACPO_OVERRIDE)
+        string(TOUPPER ${LLVM_ACPO_OVERRIDE_ARCH} arch_allcaps)
+        set(LLVM_OVERRIDE_MODEL_HEADER_${fname_allcaps}
+          ${LLVM_ACPO_OVERRIDE_PATH}/${fname}-${arch_allcaps}.h)
+        set(LLVM_OVERRIDE_MODEL_OBJECT_${fname_allcaps}
+          ${LLVM_ACPO_OVERRIDE_PATH}/${fname}-${arch_allcaps}.o)
+      endif()
+
+      tf_find_and_compile(
+        ${model_path}
+        ${LLVM_INLINER_MODEL_CURRENT_URL}
+        ${LLVM_INLINER_MODEL_PATH_DEFAULT}
+        ""
+        serve
+        "${model_signature}"
+        "${fname}"
+        "llvm::${fname}"
+      )
+    endforeach()
+  endif()
   if (DEFINED LLVM_HAVE_TF_AOT)
     tf_find_and_compile(
       ${LLVM_INLINER_MODEL_PATH}
@@ -24,6 +48,10 @@ if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
 endif()
 
 add_llvm_component_library(LLVMAnalysis
+  ACPOCollectFeatures.cpp
+  ACPOFIModel.cpp
+  ACPOMLInterface.cpp
+  ACPOModel.cpp
   AliasAnalysis.cpp
   AliasAnalysisEvaluator.cpp
   AliasSetTracker.cpp
@@ -41,6 +69,7 @@ add_llvm_component_library(LLVMAnalysis
   CGSCCPassManager.cpp
   CallGraph.cpp
   CallGraphSCCPass.cpp
+  CallHeight.cpp
   CallPrinter.cpp
   CaptureTracking.cpp
   CmpInstAnalysis.cpp
@@ -59,6 +88,8 @@ add_llvm_component_library(LLVMAnalysis
   DomPrinter.cpp
   DomTreeUpdater.cpp
   DominanceFrontier.cpp
+  DumpCallsite.cpp
+  DumpFeature.cpp
   FunctionPropertiesAnalysis.cpp
   GlobalsModRef.cpp
   GuardUtils.cpp
@@ -100,6 +131,7 @@ add_llvm_component_library(LLVMAnalysis
   MemoryProfileInfo.cpp
   MemorySSA.cpp
   MemorySSAUpdater.cpp
+  ModelDataCollector.cpp
   ModelUnderTrainingRunner.cpp
   ModuleDebugInfoPrinter.cpp
   ModuleSummaryAnalysis.cpp
diff --git a/llvm/lib/Analysis/CallHeight.cpp b/llvm/lib/Analysis/CallHeight.cpp
new file mode 100644
index 000000000000..f7b88cbdff05
--- /dev/null
+++ b/llvm/lib/Analysis/CallHeight.cpp
@@ -0,0 +1,89 @@
+//===- CallHeight.cpp - CallHeight implementation  ------------------------===//
+//
+// 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 file implements getting the call height of functions in a module.
+//
+//===----------------------------------------------------------------------===//
+#include "llvm/Analysis/CallHeight.h"
+#include "llvm/ADT/SCCIterator.h"
+#include "llvm/Analysis/CallGraph.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/InitializePasses.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "call-height"
+
+// Adapted from MLInlineAdvisor.cpp
+CallBase *getInlinableCallSite(Instruction &I) {
+  if (auto *CS = dyn_cast<CallBase>(&I)) {
+    if (Function *Callee = CS->getCalledFunction())
+      if (!Callee->isDeclaration()) {
+        return CS;
+      }
+  }
+  return nullptr;
+}
+
+unsigned CallHeight::getLevel(Function &F) { return (*Levels)[&F]; }
+
+CallHeight::CallHeight(Module &M)
+    : Levels(std::make_unique<std::map<const Function *, unsigned>>()) {
+  // Adapted from MLInlineAdvisor.cpp
+  CallGraph CG = CallGraph(M);
+
+  for (auto I = scc_begin(&CG); !I.isAtEnd(); ++I) {
+    const std::vector<CallGraphNode *> &CGNodes = *I;
+    unsigned Level = 0;
+    for (auto *CGNode : CGNodes) {
+      Function *F = CGNode->getFunction();
+      if (!F || F->isDeclaration())
+        continue;
+      for (auto &I : instructions(F)) {
+        if (auto *CS = getInlinableCallSite(I)) {
+          auto *Called = CS->getCalledFunction();
+          auto Pos = Levels->find(Called);
+          // In bottom up traversal, an inlinable callee is either in the
+          // same SCC, or to a function in a visited SCC. So not finding its
+          // level means we haven't visited it yet, meaning it's in this SCC.
+          if (Pos == Levels->end())
+            continue;
+          Level = std::max(Level, Pos->second + 1);
+        }
+      }
+    }
+    for (auto *CGNode : CGNodes) {
+      Function *F = CGNode->getFunction();
+      if (F && !F->isDeclaration())
+        (*Levels)[F] = Level;
+    }
+  }
+}
+
+AnalysisKey CallHeightAnalysis::Key;
+
+CallHeight CallHeightAnalysis::run(Module &M, ModuleAnalysisManager &MAM) {
+  return CallHeight(M);
+}
+
+bool CallHeightAnalysisWrapper::runOnModule(Module &M) {
+  Result.reset(new CallHeight(M));
+  return false;
+}
+
+void CallHeightAnalysisWrapper::getAnalysisUsage(AnalysisUsage &AU) const {
+  AU.setPreservesAll();
+}
+
+char CallHeightAnalysisWrapper::ID = 0;
+INITIALIZE_PASS(CallHeightAnalysisWrapper, DEBUG_TYPE, "Call Height Analysis",
+                false, true)
+
+Pass *llvm::createCallHeightAnalysisWrapper() {
+  return new CallHeightAnalysisWrapper();
+}
diff --git a/llvm/lib/Analysis/DumpCallsite.cpp b/llvm/lib/Analysis/DumpCallsite.cpp
new file mode 100644
index 000000000000..d49885a372f2
--- /dev/null
+++ b/llvm/lib/Analysis/DumpCallsite.cpp
@@ -0,0 +1,82 @@
+//===- DumpCallsite.cpp - DumpCallsite implementation  --------------------===//
+//
+// 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 file implements the ability to dump all callsites in a given function.
+//
+//===----------------------------------------------------------------------===//
+#include "llvm/Analysis/DumpCallsite.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/CommandLine.h"
+
+using namespace llvm;
+
+static cl::opt<bool>
+    IncludeDeclaration("include-declaration", cl::Hidden,
+                       cl::desc("Also dump declaration in dump-callsite pass"));
+
+namespace {
+
+// Implementation of actual DumpCallsite
+class DumpCallsite {
+public:
+  void run(Function &F);
+};
+
+// Wrapper for legacy PM
+class DumpCallsiteLegacy : public FunctionPass {
+public:
+  static char ID;
+  DumpCallsiteLegacy() : FunctionPass(ID) {}
+
+  bool runOnFunction(Function &F) override;
+};
+
+void DumpCallsite::run(Function &F) {
+  outs() << F.getName();
+  // Get all callees from 'call' inst
+  for (auto &I : instructions(F)) {
+    // Is a call inst
+    if (auto *CS = dyn_cast<CallBase>(&I)) {
+      // callee is present
+      if (Function *Callee = CS->getCalledFunction()) {
+        // Not intrinsic
+        if (!Callee->isIntrinsic()) {
+          // decide whether to dump declaration
+          if (!Callee->isDeclaration() || IncludeDeclaration) {
+            outs() << " " << Callee->getName();
+          }
+        }
+      }
+    }
+  }
+  outs() << "\n";
+}
+
+bool DumpCallsiteLegacy::runOnFunction(Function &F) {
+  DumpCallsite Impl;
+  Impl.run(F);
+  return false;
+}
+
+} // namespace
+
+char DumpCallsiteLegacy::ID = 0;
+INITIALIZE_PASS(DumpCallsiteLegacy, "dump-callsite", "Dump Callsite", false,
+                false)
+
+PreservedAnalyses DumpCallsitePass::run(Function &F,
+                                        FunctionAnalysisManager &FAM) {
+  DumpCallsite Impl;
+  Impl.run(F);
+  return PreservedAnalyses::all();
+}
diff --git a/llvm/lib/Analysis/DumpFeature.cpp b/llvm/lib/Analysis/DumpFeature.cpp
new file mode 100644
index 000000000000..81756226c2fd
--- /dev/null
+++ b/llvm/lib/Analysis/DumpFeature.cpp
@@ -0,0 +1,575 @@
+//===- DumpFeature.cpp - DumpFeature implementation -----------------------===//
+//
+// 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 file implements dumping features for functions in an scc.
+//
+//===----------------------------------------------------------------------===//
+#include "llvm/Analysis/DumpFeature.h"
+#include "llvm/ADT/SCCIterator.h"
+#include "llvm/Analysis/CallHeight.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/MC/MCAsmLayout.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <algorithm>
+#include <deque>
+#include <vector>
+
+using namespace llvm;
+
+bool EnableFeatureDump;
+static cl::opt<bool, true> EnableFeatureDumpFlag(
+    "enable-feature-dump", cl::location(EnableFeatureDump), cl::init(false),
+    cl::Hidden, cl::ZeroOrMore, cl::desc("Enable Feature Dump"));
+
+static cl::opt<bool>
+    CheckPairHisto("check-pair-histo", cl::Hidden,
+                   cl::desc("Dump instruction pairs in the histogram"));
+
+static cl::opt<bool> Verbose("dump-verbose", cl::Hidden,
+                             cl::desc("Dump as human readable format"));
+
+static llvm::cl::opt<std::string>
+    OutFile("feature-output", llvm::cl::desc("File for outputting features"),
+            llvm::cl::init("features.csv"));
+
+namespace {
+unsigned getMaxInstructionID() {
+#define LAST_OTHER_INST(NR) return NR;
+#include "llvm/IR/Instruction.def"
+}
+
+// This is a point in time - we determined including these pairs of
+// consecutive instructions (in the IR layout available at inline time) as
+// features improves the model performance. We want to move away from manual
+// feature selection.
+// The array is given in opcode pairs rather than labels because 1) labels
+// weren't readily available, and 2) the successions were hand - extracted.
+//
+// This array must be sorted.
+static const std::array<std::pair<size_t, size_t>, 137>
+    ImportantInstructionSuccessions{
+        {{1, 1},   {1, 4},   {1, 5},   {1, 7},   {1, 8},   {1, 9},   {1, 11},
+         {1, 12},  {1, 13},  {1, 14},  {1, 18},  {1, 20},  {1, 22},  {1, 24},
+         {1, 25},  {1, 26},  {1, 27},  {1, 28},  {1, 29},  {1, 30},  {1, 31},
+         {1, 32},  {1, 33},  {1, 34},  {1, 39},  {1, 40},  {1, 42},  {1, 45},
+         {2, 1},   {2, 2},   {2, 13},  {2, 28},  {2, 29},  {2, 32},  {2, 33},
+         {2, 34},  {2, 38},  {2, 48},  {2, 49},  {2, 53},  {2, 55},  {2, 56},
+         {13, 2},  {13, 13}, {13, 26}, {13, 33}, {13, 34}, {13, 56}, {15, 27},
+         {28, 2},  {28, 48}, {28, 53}, {29, 2},  {29, 33}, {29, 56}, {31, 31},
+         {31, 33}, {31, 34}, {31, 49}, {32, 1},  {32, 2},  {32, 13}, {32, 15},
+         {32, 28}, {32, 29}, {32, 32}, {32, 33}, {32, 34}, {32, 39}, {32, 40},
+         {32, 48}, {32, 49}, {32, 53}, {32, 56}, {33, 1},  {33, 2},  {33, 32},
+         {33, 33}, {33, 34}, {33, 49}, {33, 53}, {33, 56}, {34, 1},  {34, 2},
+         {34, 32}, {34, 33}, {34, 34}, {34, 49}, {34, 53}, {34, 56}, {38, 34},
+         {39, 57}, {40, 34}, {47, 15}, {47, 49}, {48, 2},  {48, 34}, {48, 56},
+         {49, 1},  {49, 2},  {49, 28}, {49, 32}, {49, 33}, {49, 34}, {49, 39},
+         {49, 49}, {49, 56}, {53, 1},  {53, 2},  {53, 28}, {53, 34}, {53, 53},
+         {53, 57}, {55, 1},  {55, 28}, {55, 34}, {55, 53}, {55, 55}, {55, 56},
+         {56, 1},  {56, 2},  {56, 7},  {56, 13}, {56, 32}, {56, 33}, {56, 34},
+         {56, 49}, {56, 53}, {56, 56}, {56, 64}, {57, 34}, {57, 56}, {57, 57},
+         {64, 1},  {64, 64}, {65, 1},  {65, 65}}};
+
+size_t getSize(Function &F, TargetTransformInfo &TTI) {
+  size_t SumOfAllInstCost = 0;
+  for (const auto &BB : F)
+    for (const auto &I : BB) {
+      std::optional<long int> cost =
+          TTI.getInstructionCost(
+                 &I, TargetTransformInfo::TargetCostKind::TCK_CodeSize)
+              .getValue();
+      if (cost.has_value())
+        SumOfAllInstCost += cost.value();
+    }
+  return SumOfAllInstCost;
+}
+
+unsigned getMaxDominatorTreeDepth(const Function &F,
+                                  const DominatorTree &Tree) {
+  unsigned MaxBBDepth = 0;
+  for (const auto &BB : F)
+    if (const auto *TN = Tree.getNode(&BB))
+      MaxBBDepth = std::max(MaxBBDepth, TN->getLevel());
+
+  return MaxBBDepth;
+}
+
+// get valid call uses and valid call uses in loop counts.
+std::pair<int, int>
+getValidCallUsesAndInLoopCounts(Function &F,
+                                FunctionAnalysisManager *FAM = nullptr) {
+  unsigned CallUses = 0;
+  unsigned CallUsesInLoop = 0;
+
+  for (User *U : F.users()) {
+    if (CallBase *CB = dyn_cast<CallBase>(U)) {
+      ++CallUses;
+      BasicBlock *BB = CB->getParent();
+      Function *FUser = CB->getCaller();
+      auto &LI =
+          FAM->getResult<LoopAnalysis>(*FUser);
+      if (LI.getLoopFor(BB) != nullptr) {
+        ++CallUsesInLoop;
+      }
+    }
+  }
+  return std::make_pair(CallUses, CallUsesInLoop);
+}
+} // namespace
+
+// We have: 9 calculated features (the features here); 1 feature for each
+// instruction opcode; and 1 feature for each manually-identified sequence.
+// For the latter 2, we build a histogram: we count the number of
+// occurrences of each instruction opcode or succession of instructions,
+// respectively.
+// Note that instruction opcodes start from 1. For convenience, we also have
+// an always 0 feature for the '0' opcode, hence the extra 1.
+const size_t ACPOFIExtendedFeatures::FunctionFeatures::FeatureCount =
+    ImportantInstructionSuccessions.size() + getMaxInstructionID() + 1 +
+    static_cast<size_t>(
+        ACPOFIExtendedFeatures::NamedFloatFeatureIndex::NumNamedFloatFeatures) +
+    static_cast<size_t>(
+        ACPOFIExtendedFeatures::NamedFeatureIndex::NumNamedFeatures);
+
+void ACPOFIExtendedFeatures::updateLoopRelatedFeatures(Function &F,
+                                                       LoopInfo &LI,
+                                                       FunctionFeatures &FF) {
+  uint64_t LoopNum = std::distance(LI.begin(), LI.end());
+
+  uint64_t LoopInstrCount = 0;
+  uint64_t BlockWithMulSuccNum = 0;
+  uint64_t LoopLevelSum = 0;
+  for (auto &L : LI) {
+    LoopLevelSum += static_cast<uint64_t>(L->getLoopDepth());
+    FF[NamedFeatureIndex::MaxLoopDepth] =
+        std::max(FF[NamedFeatureIndex::MaxLoopDepth],
+                 static_cast<uint64_t>(L->getLoopDepth()));
+    for (const BasicBlock *BB : L->getBlocks()) {
+      unsigned SuccCount = std::distance(succ_begin(BB), succ_end(BB));
+      if (SuccCount > 1)
+        BlockWithMulSuccNum++;
+      LoopInstrCount += std::distance(BB->instructionsWithoutDebug().begin(),
+                                      BB->instructionsWithoutDebug().end());
+    }
+  }
+
+  FF[NamedFeatureIndex::Loops] = LoopNum;
+  if (LoopNum != 0) {
+    uint64_t q = LoopInstrCount / LoopNum;
+    FF[NamedFloatFeatureIndex::InstrPerLoop] =
+        q + ((float)(LoopInstrCount - q * LoopNum)) / LoopNum;
+    q = BlockWithMulSuccNum / LoopNum;
+    FF[NamedFloatFeatureIndex::BlockWithMultipleSuccecorsPerLoop] =
+        q + ((float)(BlockWithMulSuccNum - q * LoopNum)) / LoopNum;
+    q = LoopLevelSum / LoopNum;
+    FF[NamedFloatFeatureIndex::AvgNestedLoopLevel] =
+        q + ((float)(LoopLevelSum - q * LoopNum)) / LoopNum;
+  }
+}
+
+void ACPOFIExtendedFeatures::updateBBLoopCallsiteBFFeatures(
+    Function &F, FunctionFeatures &FF, LoopInfo &LI,
+    FunctionAnalysisManager *FAM) {
+  // Initializations before looping
+  unsigned NumCallsiteInLoop = 0;
+  unsigned NumCallsite = 0;
+  uint64_t MaxCallsiteBlockFreq = 0;
+  uint64_t InstrNum = 0;
+  uint64_t SuccNum = 0;
+  uint64_t VecNum = 0;
+  uint64_t BlockNum = F.size();
+  auto getPairIndex = [](size_t a, size_t b) {
+    auto I = llvm::find(ImportantInstructionSuccessions, std::make_pair(a, b));
+    if (I == ImportantInstructionSuccessions.end())
+      return -1;
+    return static_cast<int>(
+        std::distance(ImportantInstructionSuccessions.begin(), I));
+  };
+  int StartID = 0;
+  int LastID = StartID;
+
+  // We don't want debug calls, because they'd just add noise.
+  // Sum number of instructions and successors on the way
+  for (auto &BB : F) {
+    SuccNum += std::distance(succ_begin(&BB), succ_end(&BB));
+    for (auto &I : BB.instructionsWithoutDebug()) {
+      if (CallBase *CB = dyn_cast<CallBase>(&I)) {
+        Function *Callee = CB->getCalledFunction();
+        if (Callee && !Callee->isIntrinsic()) {
+          ++NumCallsite;
+          if (!Callee->isDeclaration()) {
+            // Check all the functions that was called and get the max block
+            // frequency.
+            uint64_t EntryFreq =
+                FAM->getResult<BlockFrequencyAnalysis>(*Callee)
+                          .getEntryFreq();
+            MaxCallsiteBlockFreq = std::max(EntryFreq, MaxCallsiteBlockFreq);
+          }
+
+          if (Callee != nullptr) {
+            // Collect the number of callsites that were invoked with a pointer
+            // argument.
+            for (auto arg = Callee->arg_begin(); arg != Callee->arg_end();
+                 arg++)
+              if (isa<PointerType>(arg->getType())) {
+                FF[NamedFeatureIndex::PtrCallee]++;
+                break;
+              }
+          }
+
+          // Collect the number of callsites that returns a pointer type.
+          if (isa<PointerType>(CB->getType())) {
+            FF[NamedFeatureIndex::CallReturnPtr]++;
+          }
+
+          // Check if the given function is recursive.
+          if (&F == Callee) {
+            FF[NamedFeatureIndex::IsRecursive] = 1;
+          }
+
+          BasicBlock *BB = CB->getParent();
+          // if we found a loop for the BB that Call is in, we do +1
+          if (LI.getLoopFor(BB) != nullptr) {
+            ++NumCallsiteInLoop;
+          }
+        }
+      }
+
+      auto ID = I.getOpcode();
+      ++FF.InstructionHistogram[ID];
+      int PairIndex = getPairIndex(LastID, ID);
+      if (PairIndex >= 0)
+        ++FF.InstructionPairHistogram[PairIndex];
+      LastID = ID;
+      InstrNum++;
+      unsigned NumOp = I.getNumOperands();
+
+      // If instruction contains vector operand, consider it as a vector
+      // instruction
+      for (unsigned i = 0; i < NumOp; i++) {
+        if (isa<VectorType>(I.getOperand(i)->getType())) {
+          VecNum++;
+          break;
+        }
+      }
+
+      // If this is a conditional branch, check if it uses an argument
+      if (const auto II = dyn_cast<BranchInst>(&I))
+        if (II->isConditional()) {
+          FF[NamedFeatureIndex::ConditionalBranch]++;
+          // find the instruction where the condition is defined.
+          if (auto def = dyn_cast<Instruction>(II->getCondition())) {
+            // For all operands of def check if isa<Argument> (operand) then
+            // increment CBwithArg.
+            bool found = false;
+            for (unsigned i = 0; i < def->getNumOperands(); i++) {
+              if (isa<Argument>(def->getOperand(i))) {
+                FF[NamedFeatureIndex::CBwithArg]++;
+                found = true;
+                break;
+              }
+            }
+            if (found)
+              break;
+          }
+        }
+    }
+  }
+
+  FF[NamedFloatFeatureIndex::AvgVecInstr] = (float)VecNum / InstrNum;
+  FF[NamedFeatureIndex::Blocks] = BlockNum;
+  if (BlockNum > 0) {
+    uint64_t q = InstrNum / BlockNum;
+    FF[NamedFloatFeatureIndex::InstructionPerBlock] =
+        q + ((float)(InstrNum - q * BlockNum)) / BlockNum;
+    q = SuccNum / BlockNum;
+    FF[NamedFloatFeatureIndex::SuccessorPerBlock] =
+        q + ((float)(SuccNum - q * BlockNum)) / BlockNum;
+  }
+
+  FF[NamedFeatureIndex::MaxCallsiteBlockFreq] = MaxCallsiteBlockFreq;
+  FF[NamedFeatureIndex::NumCallsiteInLoop] = NumCallsiteInLoop;
+  FF[NamedFeatureIndex::Calls] = NumCallsite;
+}
+
+ACPOFIExtendedFeatures::FunctionFeatures
+ACPOFIExtendedFeatures::getFunctionFeatures(
+    Function &F, DominatorTree &DomTree, TargetTransformInfo &TTI, LoopInfo &LI,
+    FunctionAnalysisManager *FAM,
+    bool ValidSize, bool ValidLoop, bool ValidTree) {
+  assert(llvm::is_sorted(ImportantInstructionSuccessions) &&
+         "expected function features are sorted");
+
+  FunctionFeatures FF;
+  size_t InstrCount = getMaxInstructionID() + 1;
+  FF.InstructionHistogram.resize(InstrCount);
+  FF.InstructionPairHistogram.resize(ImportantInstructionSuccessions.size());
+
+  // check all the argument to see if there is a pointer type
+  for (auto arg = F.arg_begin(); arg != F.arg_end(); arg++) {
+    if (isa<PointerType>(arg->getType())) {
+      FF[NamedFeatureIndex::PtrArgs]++;
+    }
+  }
+
+  std::pair<int, int> ValidCallAndInLoopCounts =
+      getValidCallUsesAndInLoopCounts(F, FAM);
+  if (!ValidSize)
+    FF[NamedFeatureIndex::InitialSize] = getSize(F, TTI);
+  FF[NamedFeatureIndex::IsLocal] = F.hasLocalLinkage();
+  FF[NamedFeatureIndex::IsLinkOnceODR] = F.hasLinkOnceODRLinkage();
+  FF[NamedFeatureIndex::IsLinkOnce] = F.hasLinkOnceLinkage();
+  if (!ValidTree)
+    FF[NamedFeatureIndex::MaxDomTreeLevel] =
+        getMaxDominatorTreeDepth(F, DomTree);
+  FF[NamedFeatureIndex::CallUsage] = ValidCallAndInLoopCounts.first;
+  FF[NamedFeatureIndex::NumOfCallUsesInLoop] = ValidCallAndInLoopCounts.second;
+  FF[NamedFeatureIndex::EntryBlockFreq] =
+      FAM->getResult<BlockFrequencyAnalysis>(F)
+                .getEntryFreq();
+  ACPOFIExtendedFeatures::updateBBLoopCallsiteBFFeatures(F, FF, LI, FAM);
+  if (!ValidLoop)
+    ACPOFIExtendedFeatures::updateLoopRelatedFeatures(F, LI, FF);
+  return FF;
+}
+
+static int getCallHeight(Module &M, CallHeight *CH, Function *F) {
+  if (CH == nullptr) {
+    // If we don't have cached result (for ex, running with opt)
+    // We re-calculate the function level
+    // Or using the old pass manager
+    CallHeight CH = CallHeight(M);
+    return CH.getLevel(*F);
+  }
+  return CH->getLevel(*F);
+}
+
+void dumpInstructionPairs(raw_fd_ostream &OS) {
+  for (size_t i = 0; i < ImportantInstructionSuccessions.size(); i++) {
+    std::pair<uint64_t, uint64_t> pair = ImportantInstructionSuccessions[i];
+    OS << "{" << Instruction::getOpcodeName(pair.first) << ", "
+       << Instruction::getOpcodeName(pair.second) << "} ";
+  }
+  OS << "\n";
+}
+
+void dumpFunctionFeatures(raw_fd_ostream &OS,
+                          ACPOFIExtendedFeatures::FunctionFeatures &FF,
+                          Function &F, bool Verbose) {
+  if (Verbose) {
+    OS << "Function Name: " << F.getName() << "\n";
+    OS << "FeatureCount: " << FF.FeatureCount << "\n";
+    OS << "\nAverage instructions per basic block: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                 InstructionPerBlock]
+       << "\nAverage number of successors per block: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::SuccessorPerBlock]
+       << "\nAverage number of vector instructions per instruction: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgVecInstr]
+       << "\nAverage nest level per loop: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel]
+       << "\nAverage instructions per loop: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop]
+       << "\nAverage blocks with multiple succssors per loop: "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                 BlockWithMultipleSuccecorsPerLoop]
+       << "\nInitial Size: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize] << "\n"
+       << "Blocks: " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Blocks]
+       << "\n"
+       << "Calls (Number of callsites): "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Calls] << "\n"
+       << "IsLocal: " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLocal]
+       << "\n"
+       << "IsLinkOnceODR: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnceODR] << "\n"
+       << "IsLinkOnce: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnce] << "\n"
+       << "Loops: " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Loops]
+       << "\n"
+       << "MaxLoopDepth: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth] << "\n"
+       << "MaxDomTreeLevel: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel]
+       << "\nPointer arguments of this caller: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::PtrArgs]
+       << "\nCallees with pointer arguments: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::PtrCallee]
+       << "\nCallees that return a pointer: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallReturnPtr]
+       << "\nConditional Branches: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::ConditionalBranch]
+       << "\nConditional Branches that depends on an argument: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CBwithArg]
+       << "\nCaller Height of the current function: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallerHeight]
+       << "\nNumber of explict calls to this function: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallUsage]
+       << "\nIs recursive: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsRecursive]
+       << "\nNumber of callsites that are inside loop in this function: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::NumCallsiteInLoop]
+       << "\nNumber of explict calls to this function that are in loop: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::NumOfCallUsesInLoop]
+       << "\nBlock Frequency for the first block of this function: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::EntryBlockFreq]
+       << "\nMaximum of all callsites' entry Block Frequency: "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxCallsiteBlockFreq]
+       << "\n";
+    OS << "InstructionHistogram: ";
+    OS << "Size: " << FF.InstructionHistogram.size() << "\n";
+    for (size_t i = 0; i < FF.InstructionHistogram.size(); i++) {
+      OS << FF.InstructionHistogram[i] << " ";
+    }
+    OS << "\n";
+    OS << "InstructionPairHistogram: ";
+    OS << "Size: " << FF.InstructionPairHistogram.size() << "\n";
+    for (size_t i = 0; i < FF.InstructionPairHistogram.size(); i++) {
+      OS << FF.InstructionPairHistogram[i] << " ";
+    }
+    OS << "\n\n";
+  } else {
+    OS << F.getName() << " "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                 InstructionPerBlock]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::SuccessorPerBlock]
+       << " " << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgVecInstr]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::AvgNestedLoopLevel]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::InstrPerLoop]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFloatFeatureIndex::
+                 BlockWithMultipleSuccecorsPerLoop]
+       << " " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::InitialSize]
+       << " " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Blocks] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Calls] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLocal] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnceODR] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsLinkOnce] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::Loops] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxLoopDepth] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxDomTreeLevel] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::PtrArgs] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::PtrCallee] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallReturnPtr] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::ConditionalBranch]
+       << " " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CBwithArg] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallerHeight] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallUsage] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::IsRecursive] << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::NumCallsiteInLoop]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::NumOfCallUsesInLoop]
+       << " " << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::EntryBlockFreq]
+       << " "
+       << FF[ACPOFIExtendedFeatures::NamedFeatureIndex::MaxCallsiteBlockFreq]
+       << " ";
+
+    for (size_t i = 0; i < FF.InstructionHistogram.size(); i++) {
+      OS << FF.InstructionHistogram[i] << " ";
+    }
+    for (size_t i = 0; i < FF.InstructionPairHistogram.size(); i++) {
+      OS << FF.InstructionPairHistogram[i] << " ";
+    }
+    OS << "\n";
+  }
+}
+
+void runAndDump(raw_fd_ostream &OS, Function *F, DominatorTree &DomTree,
+                TargetTransformInfo &TTI, LoopInfo &LI, Module &M,
+                CallHeight *CH, FunctionAnalysisManager *FAM = nullptr) {
+  struct ACPOFIExtendedFeatures::FunctionFeatures FF =
+      ACPOFIExtendedFeatures::getFunctionFeatures(*F, DomTree, TTI, LI, FAM);
+  // Get the call height feature
+  FF[ACPOFIExtendedFeatures::NamedFeatureIndex::CallerHeight] =
+      getCallHeight(M, CH, F);
+  dumpFunctionFeatures(OS, FF, *F, Verbose);
+}
+
+std::unique_ptr<raw_fd_ostream> setUpOS() {
+  // Check ACPO/llvm-project issue #112
+  std::error_code FileErr;
+  std::unique_ptr<raw_fd_ostream> OS(
+      new raw_fd_ostream(OutFile.c_str(), FileErr, llvm::sys::fs::OF_Append));
+
+  if (FileErr) {
+    llvm::errs() << "Error opening info file " << OutFile.c_str() << ": "
+                 << FileErr.message() << "\n";
+    return nullptr;
+  }
+
+  if (CheckPairHisto) {
+    dumpInstructionPairs(*OS);
+    return nullptr;
+  }
+
+  return OS;
+}
+
+PreservedAnalyses DumpFeaturePass::run(LazyCallGraph::SCC &C,
+                                       CGSCCAnalysisManager &AM,
+                                       LazyCallGraph &CG,
+                                       CGSCCUpdateResult &UR) {
+  std::unique_ptr<raw_fd_ostream> OS = setUpOS();
+  if (!OS)
+    return PreservedAnalyses::all();
+
+  FunctionAnalysisManager &FAM =
+      AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
+
+  const auto &MAMProxy = AM.getResult<ModuleAnalysisManagerCGSCCProxy>(C, CG);
+  Module &M = *C.begin()->getFunction().getParent();
+  CallHeight *CH = MAMProxy.getCachedResult<CallHeightAnalysis>(M);
+  for (LazyCallGraph::Node &N : C) {
+    Function *F = &N.getFunction();
+    if (F->empty()) {
+      continue;
+    }
+
+    auto &DomTree = FAM.getResult<DominatorTreeAnalysis>(*F);
+    auto &TTI = FAM.getResult<TargetIRAnalysis>(*F);
+    auto &LI = FAM.getResult<LoopAnalysis>(*F);
+
+    runAndDump(*OS, F, DomTree, TTI, LI, M, CH, &FAM);
+  }
+  return PreservedAnalyses::all();
+}
+
+ACPOFIExtendedFeatures::NamedFeatureIndex &
+llvm::operator++(ACPOFIExtendedFeatures::NamedFeatureIndex &n) {
+  return n = static_cast<ACPOFIExtendedFeatures::NamedFeatureIndex>((int)n + 1);
+}
+
+ACPOFIExtendedFeatures::NamedFeatureIndex
+operator++(ACPOFIExtendedFeatures::NamedFeatureIndex &n, int) {
+  ACPOFIExtendedFeatures::NamedFeatureIndex res = n;
+  ++n;
+  return res;
+}
+
+ACPOFIExtendedFeatures::NamedFloatFeatureIndex &
+llvm::operator++(ACPOFIExtendedFeatures::NamedFloatFeatureIndex &n) {
+  return n = static_cast<ACPOFIExtendedFeatures::NamedFloatFeatureIndex>((int)n + 1);
+}
+
+ACPOFIExtendedFeatures::NamedFloatFeatureIndex
+operator++(ACPOFIExtendedFeatures::NamedFloatFeatureIndex &n, int) {
+  ACPOFIExtendedFeatures::NamedFloatFeatureIndex res = n;
+  ++n;
+  return res;
+}
diff --git a/llvm/lib/Analysis/ModelDataCollector.cpp b/llvm/lib/Analysis/ModelDataCollector.cpp
new file mode 100644
index 000000000000..5d599bff25a4
--- /dev/null
+++ b/llvm/lib/Analysis/ModelDataCollector.cpp
@@ -0,0 +1,350 @@
+//===- ModelDataCollector.cpp - Data collector for ML model  --------------===//
+//
+// 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 file implements the collection and dumping of data for the ML models
+//
+//===----------------------------------------------------------------------===//
+
+#if defined(ENABLE_ACPO)
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/ModelDataCollector.h"
+#include "llvm/Demangle/Demangle.h"
+#include "llvm/IR/Function.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "model-data-collector"
+
+// Defined in 'lib/IR/AsmWriter.cpp'
+extern cl::opt<std::string> UnnamedVariablePrefix;
+
+static cl::opt<std::string> IRFileDirectory(
+    "IR-file-directory", cl::Hidden,
+    cl::desc("Name of a directory to store IR files."));
+
+cl::opt<std::string>
+    ACPOModelFile("acpo-dump-file", cl::init("-"), cl::Hidden,
+                  cl::desc("Name of a file to store feature data in."));
+
+std::string ModelDataCollector::getDumpOptionAsString(DumpOption DO) {
+  switch (DO) {
+    case DumpOption::loop:
+      return "loop";
+    case DumpOption::function:
+      return "function";
+    case DumpOption::before:
+      return "before";
+    case DumpOption::after:
+      return "after";
+    default:
+      return "";
+  }
+}
+
+std::vector<std::pair<std::string, std::string>> ModelDataCollector::getFeatures() {
+  return Features;
+}
+
+StringMap<std::string> ModelDataCollector::getIRFileNameMap() {
+  return IRFileNames;
+}
+
+std::string ModelDataCollector::getOutputFileName() { return OutputFileName; }
+
+bool ModelDataCollector::isEmptyOutputFile() {
+  if (OutputFileName.empty())
+    return false;
+
+  if (!sys::fs::exists(OutputFileName))
+    return true;
+
+  uint64_t Size;
+  std::error_code EC = sys::fs::file_size(OutputFileName, Size);
+  if (EC) {
+    llvm::errs() << "Cannot get file size: " << EC.message() << "\n";
+    assert(false && "Cannot get file size.");
+  }
+
+  if (Size == 0)
+    return true;
+
+  return false;
+}
+
+void ModelDataCollector::collectFeatures(Loop *L, const std::string &ModuleName,
+                        const std::string &FuncName, const std::string &LoopName) {
+}
+
+void ModelDataCollector::collectFeatures() {
+  for (auto &FeatureCollectInfo : FeatureCollectInfos) {
+    ACPOCollectFeatures::FeatureValueMap FeatureMap;
+
+    if (FeatureCollectInfo->FeaturesInfo.get()) {
+      FeatureMap = FeatureCollectInfo->FeatureCollector->getFeaturesPair(
+          *FeatureCollectInfo->FeaturesInfo.get());
+    } else if (FeatureCollectInfo->RegisteredScopes.get()) {
+      FeatureCollectInfo->FeatureCollector->setGlobalFeatureInfo(
+          *FeatureCollectInfo->GlobalInfo.get());
+      FeatureMap = FeatureCollectInfo->FeatureCollector->getFeaturesPair(
+          *FeatureCollectInfo->RegisteredScopes.get());
+    } else if (FeatureCollectInfo->RegisteredGroupIDs.get()) {
+      FeatureCollectInfo->FeatureCollector->setGlobalFeatureInfo(
+          *FeatureCollectInfo->GlobalInfo.get());
+      FeatureMap = FeatureCollectInfo->FeatureCollector->getFeaturesPair(
+          *FeatureCollectInfo->RegisteredGroupIDs.get());
+    } else {
+      outs() << "No Features are collected, since the given "
+                "FeatureCollectInfo is invalid.\n";
+      return;
+    }
+    for (auto const &[key, val] : FeatureMap) {
+      std::string FeatureName;
+
+      if (FeatureCollectInfo->Prefix != "")
+        FeatureName += FeatureCollectInfo->Prefix + "_";
+
+      FeatureName += ACPOCollectFeatures::getFeatureName(key);
+
+      if (FeatureCollectInfo->Postfix != "")
+        FeatureName += "_" + FeatureCollectInfo->Postfix;
+
+      Features.insert(Features.end(), {std::make_pair(FeatureName, val)});
+    }
+  }
+}
+
+void ModelDataCollector::registerFeature(ACPOCollectFeatures::FeaturesInfo Info,
+                                         std::string Pre, std::string Post) {
+  std::unique_ptr<ModelDataCollector::FeatureCollectInfo> tmp =
+      std::make_unique<ModelDataCollector::FeatureCollectInfo>();
+  tmp->FeaturesInfo.reset(new ACPOCollectFeatures::FeaturesInfo{Info});
+  tmp->FeatureCollector.reset(new ACPOCollectFeatures{});
+  tmp->Prefix = Pre;
+  tmp->Postfix = Post;
+
+  FeatureCollectInfos.push_back(std::move(tmp));
+}
+
+void ModelDataCollector::registerFeature(
+    ACPOCollectFeatures::Scopes ScopeVec,
+    ACPOCollectFeatures::FeatureInfo GlobalInfo, std::string Pre,
+    std::string Post) {
+  std::unique_ptr<ModelDataCollector::FeatureCollectInfo> tmp =
+      std::make_unique<ModelDataCollector::FeatureCollectInfo>();
+  tmp->RegisteredScopes.reset(new ACPOCollectFeatures::Scopes{ScopeVec});
+  tmp->FeatureCollector.reset(new ACPOCollectFeatures{});
+  tmp->GlobalInfo.reset(new ACPOCollectFeatures::FeatureInfo{GlobalInfo});
+  tmp->Prefix = Pre;
+  tmp->Postfix = Post;
+
+  FeatureCollectInfos.push_back(std::move(tmp));
+}
+
+void ModelDataCollector::registerFeature(
+    ACPOCollectFeatures::GroupIDs GroupIDVec,
+    ACPOCollectFeatures::FeatureInfo GlobalInfo, std::string Pre,
+    std::string Post) {
+  std::unique_ptr<ModelDataCollector::FeatureCollectInfo> tmp =
+      std::make_unique<ModelDataCollector::FeatureCollectInfo>();
+  tmp->RegisteredGroupIDs.reset(new ACPOCollectFeatures::GroupIDs{GroupIDVec});
+  tmp->FeatureCollector.reset(new ACPOCollectFeatures{});
+  tmp->GlobalInfo.reset(new ACPOCollectFeatures::FeatureInfo{GlobalInfo});
+  tmp->Prefix = Pre;
+  tmp->Postfix = Post;
+
+  FeatureCollectInfos.push_back(std::move(tmp));
+}
+
+void ModelDataCollector::resetRegisteredFeatures() {
+  FeatureCollectInfos.clear();
+  Features.clear();
+}
+
+std::string ModelDataCollector::demangleName(const std::string &Name) {
+  ItaniumPartialDemangler D;
+  if (!D.partialDemangle(Name.c_str()))
+    return D.getFunctionBaseName(nullptr, nullptr);
+
+  return Name;
+}
+
+void ModelDataCollector::setFeatures(
+                std::vector<std::pair<std::string, std::string>> NewFeatures) {
+  Features = NewFeatures;
+}
+
+void ModelDataCollector::addFeatures(
+                std::vector<std::pair<std::string, std::string>> NewFeatures) {
+  Features.insert(Features.end(), NewFeatures.begin(), NewFeatures.end());
+}
+
+void ModelDataCollector::setIRFileNameMap(StringMap<std::string> IRFileNameMap) {
+  IRFileNames = IRFileNameMap;
+}
+
+void ModelDataCollector::printRow(bool printHeader) {
+  // Print the IR file names first
+  for (const auto &P : IRFileNames) {
+    if (printHeader)
+      Out << P.getKey();
+    else
+      Out << P.getValue();
+
+    Out << ",";
+  }
+
+  for (unsigned I = 0, E = Features.size(); I != E; ++I ) {
+    // First value does not get a comma
+    if (I)
+      Out << ",";
+
+    if (printHeader)
+      Out << Features.at(I).first;
+    else
+      Out << Features.at(I).second;
+  }
+
+  Out << "\n";
+}
+
+/*std::string ModelDataCollector::generateIRFileName(autotuning::CodeRegion CR) {
+  // File name = source_location + pass_name + coderegion_type + hash,
+  // where source_location = file_name + func_name + loop_name
+  //                         + line_number + column_number
+  std::string IRFileName =
+      sys::path::filename(StringRef(CR.getFileName())).str() + "_"
+      + demangleName(CR.getFuncName()) + "_"
+      + CR.getName() + "_"
+      + std::to_string(CR.getSourceLoc().SourceLine) + "_"
+      + std::to_string(CR.getSourceLoc().SourceColumn) + "_"
+      + CR.getPassName() + "_"
+      + CR.getTypeAsString() + "_"
+      + std::to_string(CR.getHash()) + ".ll";
+  return IRFileName;
+}*/
+
+std::string ModelDataCollector::getIRFileName(StringRef Key) {
+  if (IRFileNames.count(Key))
+    return IRFileNames.find(Key)->second;
+
+  return "None";
+}
+
+std::unique_ptr<raw_ostream>
+ModelDataCollector::createFile(const Twine &FilePath,
+                               const Twine &FileName,
+                               std::error_code &EC) {
+  if (std::error_code EC = sys::fs::create_directories(FilePath))
+    errs() << "Error creating directory: " << FilePath << ": "
+           << EC.message() << "\n";
+
+  return std::make_unique<raw_fd_ostream>((FilePath + "/" + FileName).str(), EC);
+}
+
+void ModelDataCollector::createIRFileForLoop(Loop *L, const Twine &IRFilePath,
+                                             const Twine &IRFileName,
+                                             bool OverwriteIRFile) {
+  if (!OverwriteIRFile && sys::fs::exists(IRFilePath + "/" + IRFileName))
+    return;
+
+  // Write IR to file
+  std::error_code EC;
+  auto OS = createFile(IRFilePath, Twine(IRFileName), EC);
+  if (EC) {
+    errs() << "Error creating loop IR file: " << IRFileName << ": "
+            << EC.message() << "\n";
+    return;
+  }
+
+  // Print loop wrapped in function if -unnamed-var-prefix is set by user
+  if (UnnamedVariablePrefix.getNumOccurrences() > 0) {
+    SmallVector<BasicBlock *, 8> ExitBlocks;
+    L->getExitBlocks(ExitBlocks);
+    // May need to move this code out of Loop data structure in LLVM. Will see.
+    L->printWithFunctionWrapper(*OS, L->getHeader()->getParent(),
+                                L->getBlocks(), L->getHeader(), ExitBlocks,
+                                /*AAW*/ nullptr,
+                                /*ShouldPreserveUseListOrder*/ false,
+                                /*IsForDebug*/ false);
+  } else {
+    L->print(*OS, /*Depth*/ 0, /*Verbose*/ true);
+  }
+}
+
+void ModelDataCollector::createIRFileForFunction(Function *F,
+                                                 const Twine &IRFilePath,
+                                                 const Twine &IRFileName,
+                                                 bool OverwriteIRFile) {
+  if (!OverwriteIRFile && sys::fs::exists(IRFilePath + "/" + IRFileName))
+    return;
+
+  // Write IR to file
+  std::error_code EC;
+  auto OS = createFile(IRFilePath, Twine(IRFileName), EC);
+  if (EC) {
+    errs() << "Error creating function IR file: " << IRFileName << ": "
+            << EC.message() << "\n";
+    return;
+  }
+  // May need to investigate this print function change.
+  F->print(*OS, /*AAW*/ nullptr, /*ShouldPreserveUseListOrder*/ false,
+           /*IsForDebug*/ false);
+}
+
+void ModelDataCollector::writeIR(Loop *L, Function *F,
+                                 std::string NewIRFileName,
+                                 std::string PassName,
+                                 DumpOption DumpBeforeOrAfter, bool PrintLoop,
+                                 bool PrintFunction, bool OverwriteIRFile) {
+  // Create base directory first
+  SmallString<256> IRFilePath;
+  if (IRFileDirectory.getNumOccurrences() > 0) {
+    // Third priority is the directory specified by
+    // the -IR-file-directory option
+    Twine BaseDir(IRFileDirectory);
+    BaseDir.toVector(IRFilePath);
+  } else {
+    // No directory specified
+    return;
+  }
+
+  if (getDumpOptionAsString(DumpBeforeOrAfter).empty())
+    return;
+
+  // Create sub-directories to store corresponding IR files.
+  // Directory name = before/after + pass_name + coderegion_type
+  std::string SubDir = getDumpOptionAsString(DumpBeforeOrAfter)
+                                  + "_" + PassName;
+  if (L && PrintLoop) {
+    createIRFileForLoop(L,
+                        Twine(IRFilePath) + "/" + SubDir + "_" +
+                            getDumpOptionAsString(DumpOption::loop),
+                        Twine(NewIRFileName), OverwriteIRFile);
+    // Add IR file name for summary data file
+    IRFileNames.insert(std::pair<std::string, std::string> (
+                        getDumpOptionAsString(DumpBeforeOrAfter)
+                        + getDumpOptionAsString(DumpOption::loop),
+                        NewIRFileName));
+  }
+
+  if (F && PrintFunction) {
+    createIRFileForFunction(F,
+                            Twine(IRFilePath) + "/" + SubDir + "_" +
+                                getDumpOptionAsString(DumpOption::function),
+                            Twine(NewIRFileName), OverwriteIRFile);
+    // Add IR file name for summary data file
+    IRFileNames.insert(std::pair<std::string, std::string> (
+                        getDumpOptionAsString(DumpBeforeOrAfter)
+                        + getDumpOptionAsString(DumpOption::function),
+                        NewIRFileName));
+  }
+}
+#endif // ENABLE_ACPO
diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt
index 9029dc7bb3d9..579074408b55 100644
--- a/llvm/lib/CodeGen/CMakeLists.txt
+++ b/llvm/lib/CodeGen/CMakeLists.txt
@@ -1,4 +1,4 @@
-if (DEFINED LLVM_HAVE_TF_AOT OR LLVM_HAVE_TFLITE)
+if ((DEFINED LLVM_HAVE_TF_AOT OR DEFINED LLVM_HAVE_TF_API) AND (NOT ACPO_AOT))
   include(TensorFlowCompile)
   set(LLVM_RAEVICT_MODEL_PATH_DEFAULT "models/regalloc-eviction")
 
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index af77e6c2dc4d..a02c603a14a5 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -86,8 +86,16 @@
 #include <utility>
 #include <vector>
 
+#include "llvm/ADT/StringSet.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Support/CommandLine.h"
+
 using namespace llvm;
 
+cl::opt<std::string> UnnamedVariablePrefix(
+    "unnamed-var-prefix", cl::Hidden,
+    cl::desc("Specify the prefix added to unnamed variables"), cl::init(""));
+
 // Make virtual table appear in this compilation unit.
 AssemblyAnnotationWriter::~AssemblyAnnotationWriter() = default;
 
@@ -2487,9 +2495,11 @@ static void WriteAsOperandInternal(raw_ostream &Out, const Value *V,
     Slot = -1;
   }
 
-  if (Slot != -1)
-    Out << Prefix << Slot;
-  else
+  if (Slot != -1) {
+    // By default, UnnamedVariablePrefix is empty so it matches original behaviour
+    // unless specified.
+    Out << Prefix << UnnamedVariablePrefix << Slot;
+  } else
     Out << "<badref>";
 }
 
@@ -2602,12 +2612,13 @@ public:
   void writeAllAttributeGroups();
 
   void printTypeIdentities();
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   void printGlobal(const GlobalVariable *GV, bool PrintDeclarationOnly = false);
   void printAlias(const GlobalAlias *GA);
   void printIFunc(const GlobalIFunc *GI);
   void printComdat(const Comdat *C);
-  void printRequisiteDeclarations(const Function *F);
+  void printRequisiteDeclarations(const Function *F,
+                                  std::vector<BasicBlock *> LoopBlocks = {});  
   void printFunction(const Function *F, bool PrintCompleteIR = false,
                      bool PrintDeclarationOnly = false);
 #else
@@ -2616,9 +2627,15 @@ public:
   void printIFunc(const GlobalIFunc *GI);
   void printComdat(const Comdat *C);
   void printFunction(const Function *F);
+#endif
+#if defined(ENABLE_ACPO)
+  void printLoopWithFunctionWrapper(Function *F,
+                                    std::vector<BasicBlock *> LoopBlocks,
+                                    BasicBlock *Header,
+                                    SmallVector<BasicBlock *, 8> ExitBlocks);
 #endif
   void printArgument(const Argument *FA, AttributeSet Attrs);
-  void printBasicBlock(const BasicBlock *BB);
+  void printBasicBlock(const BasicBlock *BB, bool PrintLabelOnly = false);
   void printInstructionLine(const Instruction &I);
   void printInstruction(const Instruction &I);
 
@@ -3603,7 +3620,7 @@ static void maybePrintComdat(formatted_raw_ostream &Out,
   Out << ')';
 }
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
 void AssemblyWriter::printGlobal(const GlobalVariable *GV,
                                  bool PrintDeclarationOnly) {
   if (GV->isMaterializable() && !PrintDeclarationOnly)
@@ -3617,7 +3634,7 @@ void AssemblyWriter::printGlobal(const GlobalVariable *GV) {
   WriteAsOperandInternal(Out, GV, WriterCtx);
   Out << " = ";
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   if ((!GV->hasInitializer() || PrintDeclarationOnly) &&
       GV->hasExternalLinkage())
 #else
@@ -3640,7 +3657,7 @@ void AssemblyWriter::printGlobal(const GlobalVariable *GV) {
   Out << (GV->isConstant() ? "constant " : "global ");
   TypePrinter.print(GV->getValueType(), Out);
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   if (GV->hasInitializer() && !PrintDeclarationOnly) {
 #else
   if (GV->hasInitializer()) {
@@ -3794,21 +3811,34 @@ void AssemblyWriter::printTypeIdentities() {
   }
 }
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
 /// printRequisiteDeclarations - Print the declarations of type identities,
 /// global variables, functions, and function attribute groups of a function.
-void AssemblyWriter::printRequisiteDeclarations(const Function *F) {
+void AssemblyWriter::printRequisiteDeclarations(
+    const Function *F, std::vector<BasicBlock *> LoopBlocks) {
   // walk through instructions and collect global variables & functions
   SmallPtrSet<GlobalVariable *, 8> GVs;
   SmallPtrSet<Function *, 8> Functions;
-  for (const BasicBlock &BB : *F) {
-    for (const Instruction &I : BB) {
+  std::vector<BasicBlock *> BasicBlocks;
+  if (!LoopBlocks.empty()) {
+    for (BasicBlock *BB : LoopBlocks)
+      BasicBlocks.push_back(BB);
+  } else {
+    for (const BasicBlock &BB : *F)
+      BasicBlocks.push_back(const_cast<BasicBlock *>(&BB));
+  }
+
+  for (const BasicBlock *BB : BasicBlocks) {
+    for (const Instruction &I : *BB) {
       // Check for function
       if (const auto *CI = dyn_cast<CallInst>(&I)) {
         Function *func = CI->getCalledFunction();
         if (func)
           Functions.insert(func);
       }
+      if (const InvokeInst *II = dyn_cast<InvokeInst>(&I))
+        if (Function *func = dyn_cast<Function>(II->getCalledOperand()))
+          Functions.insert(func);
       // Check for global variables
       for (const Use &U : I.operands()) {
         if (GlobalVariable *gv = dyn_cast<GlobalVariable>(U))
@@ -3823,6 +3853,16 @@ void AssemblyWriter::printRequisiteDeclarations(const Function *F) {
               GVs.insert(gv);
           }
         }
+        // Check for ConstantExpr BitCast
+        if (const auto *CstExpr = dyn_cast<ConstantExpr>(U))
+          if (CstExpr->isCast())
+            for (const Use &UU : CstExpr->operands()) {
+              if (GlobalVariable *gv = dyn_cast<GlobalVariable>(UU))
+                GVs.insert(gv);
+              else if (const Function *func =
+                           dyn_cast<Function>(CstExpr->stripPointerCasts()))
+                Functions.insert(const_cast<Function *>(func));
+            }
       }
     }
   }
@@ -3842,7 +3882,7 @@ void AssemblyWriter::printRequisiteDeclarations(const Function *F) {
       // modify property if needed
       if (!(*GVit)->hasAvailableExternallyLinkage() &&
           !((*GVit)->getName() == "llvm.global_ctors") &&
-          (*GVit)->hasLocalLinkage()) {
+          ((*GVit)->hasLocalLinkage() || (*GVit)->hasCommonLinkage())) {
         (*GVit)->setLinkage(GlobalValue::ExternalLinkage);
         (*GVit)->setVisibility(GlobalValue::HiddenVisibility);
       }
@@ -3860,8 +3900,14 @@ void AssemblyWriter::printRequisiteDeclarations(const Function *F) {
   // print functions
   for (auto FuncIt = Functions.begin(), et = Functions.end(); FuncIt != et;
        ++FuncIt) {
+    if (!LoopBlocks.empty() && *FuncIt == F)
+      continue;
     Out << '\n';
+    GlobalValue::LinkageTypes SavedLinkage = (*FuncIt)->getLinkage();
+    // Function declarations can only have external or extern_weak linkage
+    (*FuncIt)->setLinkage(GlobalValue::ExternalLinkage);
     printFunction(*FuncIt, false, true);
+    (*FuncIt)->setLinkage(SavedLinkage);
   }
 
   // Write attribute groups.
@@ -3873,7 +3919,8 @@ void AssemblyWriter::printRequisiteDeclarations(const Function *F) {
 }
 
 /// printFunction - Print all aspects of a function.
-void AssemblyWriter::printFunction(const Function *F, bool PrintCompleteIR,
+void AssemblyWriter::printFunction(const Function *F,
+                                   bool PrintCompleteIR,
                                    bool PrintDeclarationOnly) {
   if (PrintCompleteIR && !PrintDeclarationOnly) {
     printRequisiteDeclarations(F);
@@ -3887,6 +3934,9 @@ void AssemblyWriter::printFunction(const Function *F, bool PrintCompleteIR,
 void AssemblyWriter::printFunction(const Function *F) {
   if (AnnotationWriter) AnnotationWriter->emitFunctionAnnot(F, Out);
 
+  if (AnnotationWriter)
+    AnnotationWriter->emitFunctionAnnot(F, Out);
+
   if (F->isMaterializable())
     Out << "; Materializable\n";
 #endif
@@ -3907,7 +3957,7 @@ void AssemblyWriter::printFunction(const Function *F) {
       Out << "; Function Attrs: " << AttrStr << '\n';
   }
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   if (!PrintDeclarationOnly)
     Machine.incorporateFunction(F);
 
@@ -3952,7 +4002,7 @@ void AssemblyWriter::printFunction(const Function *F) {
   Out << '(';
 
   // Loop over the arguments, printing them...
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   if ((F->isDeclaration() && !IsForDebug) || PrintDeclarationOnly) {
 #else
   if (F->isDeclaration() && !IsForDebug) {
@@ -4027,7 +4077,7 @@ void AssemblyWriter::printFunction(const Function *F) {
     writeOperand(F->getPersonalityFn(), /*PrintType=*/true);
   }
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   if (F->isDeclaration() || PrintDeclarationOnly) {
 #else
   if (F->isDeclaration()) {
@@ -4049,16 +4099,102 @@ void AssemblyWriter::printFunction(const Function *F) {
     Out << "}\n";
   }
 
-#if defined(ENABLE_AUTOTUNER)
+#if defined(ENABLE_AUTOTUNER) || defined(ENABLE_ACPO)
   // Output metadata
   if (!Machine.mdn_empty() && PrintCompleteIR && !PrintDeclarationOnly) {
     Out << '\n';
     writeAllMDNodes();
   }
-#endif
+  if (!PrintDeclarationOnly)
+    Machine.purgeFunction();
+#else
   Machine.purgeFunction();
+#endif
 }
 
+#if defined(ENABLE_ACPO)
+/// printLoopWithFunctionWrapper - print out a loop wrapped in a dummy
+/// function. All global/local variables, functions and metadata that
+/// are referenced inside the loop are printed out. Loop predecessors
+/// and loop exit blocks are also included.
+void AssemblyWriter::printLoopWithFunctionWrapper(
+    Function *F, std::vector<BasicBlock *> LoopBlocks, BasicBlock *Header,
+    SmallVector<BasicBlock *, 8> ExitBlocks) {
+  printRequisiteDeclarations(F, LoopBlocks);
+
+  // Output the dummy function
+  bool IsFirstArgument = true;
+  Out << "define void ";
+  std::string FunctionName = "foo";
+  PrintLLVMName(Out, FunctionName, GlobalPrefix);
+  Out << '(';
+  for (const Argument &Arg : F->args()) {
+    if (IsFirstArgument) // Add commas if there are more than one
+      IsFirstArgument = false;
+    else
+      Out << ", ";
+      Out << &Arg;
+  }
+
+  // All local variables referenced in this loop but are not declared here
+  // are printed next in the argument list
+  SmallPtrSet<const Instruction *, 32> AddedVariables;
+  for (const BasicBlock *BB : LoopBlocks)
+    for (const Instruction &I : *BB)
+      for (unsigned i = 0, e = I.getNumOperands(); i != e; ++i) {
+        Value *Op = I.getOperand(i);
+        // Print out the operand in the function argument list
+        // if it is an instruction that is not contained in this loop
+        if (const Instruction *II = dyn_cast_or_null<Instruction>(Op))
+          if (!AddedVariables.contains(II) &&
+              std::find(LoopBlocks.begin(), LoopBlocks.end(),
+                        II->getParent()) != LoopBlocks.end()) {
+            AddedVariables.insert(II);
+            if (IsFirstArgument)
+              IsFirstArgument = false;
+            else
+              Out << ", ";
+
+            writeOperand(Op, true);
+          }
+      }
+
+  Out << ") {\n";
+
+  // Output loop predecessors
+  // Each predecessor only needs to have an unconditional 'br' instruction
+  // that branches to the loop header
+  for (const BasicBlock *Pred : children<Inverse<BasicBlock *>>(Header))
+    // If the block is not in the loop
+    if (std::find(LoopBlocks.begin(), LoopBlocks.end(), Pred) !=
+        LoopBlocks.end()) {
+      printBasicBlock(Pred, true);
+      Out << "  br label %";
+      PrintLLVMName(Out, Header->getName(), LabelPrefix);
+      Out << "\n";
+    }
+
+  // Output all of the loop's basic blocks
+  for (const BasicBlock *BB : LoopBlocks)
+    printBasicBlock(BB);
+
+  // Output loop exit blocks
+  // Each exit block only needs a 'ret' instruction
+  for (const BasicBlock *Succ : ExitBlocks) {
+    printBasicBlock(Succ, true);
+    Out << "  ret void\n";
+  }
+
+  Out << "}\n";
+
+  // Output metadata
+  if (!Machine.mdn_empty()) {
+    Out << '\n';
+    writeAllMDNodes();
+  }
+}
+#endif
+
 /// printArgument - This member is called for every argument that is passed into
 /// the function.  Simply print it out
 void AssemblyWriter::printArgument(const Argument *Arg, AttributeSet Attrs) {
@@ -4078,13 +4214,17 @@ void AssemblyWriter::printArgument(const Argument *Arg, AttributeSet Attrs) {
   } else {
     int Slot = Machine.getLocalSlot(Arg);
     assert(Slot != -1 && "expect argument in function here");
-    Out << " %" << Slot;
+    // By default, UnnamedVariablePrefix is empty so it matches original behaviour
+    // unless specified.
+    Out << " %" << UnnamedVariablePrefix << Slot;
   }
 }
 
 /// printBasicBlock - This member is called for each basic block in a method.
-void AssemblyWriter::printBasicBlock(const BasicBlock *BB) {
-  bool IsEntryBlock = BB->getParent() && BB->isEntryBlock();
+void AssemblyWriter::printBasicBlock(const BasicBlock *BB,
+                                     bool PrintLabelOnly) {
+  assert(BB && BB->getParent() && "block without parent!");
+  bool IsEntryBlock = BB == &BB->getParent()->getEntryBlock();
   if (BB->hasName()) {              // Print out the label if it exists...
     Out << "\n";
     PrintLLVMName(Out, BB->getName(), LabelPrefix);
@@ -4092,12 +4232,19 @@ void AssemblyWriter::printBasicBlock(const BasicBlock *BB) {
   } else if (!IsEntryBlock) {
     Out << "\n";
     int Slot = Machine.getLocalSlot(BB);
-    if (Slot != -1)
-      Out << Slot << ":";
-    else
+    if (Slot != -1) {
+      // By default, UnnamedVariablePrefix is empty so it matches original behaviour
+      // unless specified.
+      Out << UnnamedVariablePrefix << Slot << ":";
+    } else
       Out << "<badref>:";
   }
 
+  if (PrintLabelOnly) {
+    Out << "\n";
+    return;
+  }
+
   if (!IsEntryBlock) {
     // Output predecessors for the block.
     Out.PadToColumn(50);
@@ -4191,8 +4338,11 @@ void AssemblyWriter::printInstruction(const Instruction &I) {
     int SlotNum = Machine.getLocalSlot(&I);
     if (SlotNum == -1)
       Out << "<badref> = ";
-    else
-      Out << '%' << SlotNum << " = ";
+    else {
+      // By default, UnnamedVariablePrefix is empty so it matches original behaviour
+      // unless specified.
+      Out << '%' << UnnamedVariablePrefix << SlotNum << " = ";
+    }
   }
 
   if (const CallInst *CI = dyn_cast<CallInst>(&I)) {
@@ -4762,6 +4912,20 @@ void BasicBlock::print(raw_ostream &ROS, AssemblyAnnotationWriter *AAW,
   W.printBasicBlock(this);
 }
 
+#if defined(ENABLE_ACPO)
+void Loop::printWithFunctionWrapper(
+    raw_ostream &ROS, Function *F, ArrayRef<BasicBlock *> LoopBlocks,
+    BasicBlock *Header, SmallVector<BasicBlock *, 8> ExitBlocks,
+    AssemblyAnnotationWriter *AAW, bool ShouldPreserveUseListOrder,
+    bool IsForDebug) const {
+  SlotTracker SlotTable(F);
+  formatted_raw_ostream OS(ROS);
+  AssemblyWriter W(OS, SlotTable, F->getParent(), AAW, IsForDebug,
+                   ShouldPreserveUseListOrder);
+  W.printLoopWithFunctionWrapper(F, LoopBlocks, Header, ExitBlocks);
+}
+#endif
+
 void Module::print(raw_ostream &ROS, AssemblyAnnotationWriter *AAW,
                    bool ShouldPreserveUseListOrder, bool IsForDebug) const {
   SlotTracker SlotTable(this);
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index a3ccbc6d258f..e2fe3322aef4 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -267,6 +267,12 @@
 #include "llvm/Transforms/Scalar/AutoTuningCompile.h"
 #endif
 
+#if defined(ENABLE_ACPO)
+#include "llvm/Analysis/CallHeight.h"
+#include "llvm/Analysis/DumpCallsite.h"
+#include "llvm/Analysis/DumpFeature.h"
+#endif
+
 using namespace llvm;
 
 static const Regex DefaultAliasRegex(
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 8009e011833c..de89f5393ba2 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -138,6 +138,12 @@
 #include "llvm/Transforms/Scalar/AutoTuningCompile.h"
 #endif
 
+#if defined(ENABLE_ACPO)
+#include "llvm/Analysis/CallHeight.h"
+#include "llvm/Analysis/DumpCallsite.h"
+#include "llvm/Analysis/DumpFeature.h"
+#endif
+
 using namespace llvm;
 
 static cl::opt<InliningAdvisorMode> UseInlineAdvisor(
@@ -894,6 +900,14 @@ PassBuilder::buildInlinerPipeline(OptimizationLevel Level,
   // make a lot of sense and we should revisit the core CGSCC structure.
   CGSCCPassManager &MainCGPipeline = MIWP.getPM();
 
+#if defined(ENABLE_ACPO)
+  if (EnableFeatureDump) {
+    // Add CallHeight analysis for dump feature
+    MIWP.addModulePass(RequireAnalysisPass<CallHeightAnalysis, Module>());
+    MainCGPipeline.addPass(DumpFeaturePass());
+  }
+#endif
+
   // Note: historically, the PruneEH pass was run first to deduce nounwind and
   // generally clean up exception handling overhead. It isn't clear this is
   // valuable as the inliner doesn't currently care whether it is inlining an
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 45a539f14b93..6ef0d6791ff2 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -33,6 +33,10 @@ MODULE_ANALYSIS("ir-similarity", IRSimilarityAnalysis())
 MODULE_ANALYSIS("autotuning-dump", AutotuningDumpAnalysis())
 #endif
 
+#if defined(ENABLE_ACPO)
+MODULE_ANALYSIS("call-height", CallHeightAnalysis())
+#endif
+
 #ifndef MODULE_ALIAS_ANALYSIS
 #define MODULE_ALIAS_ANALYSIS(NAME, CREATE_PASS)                               \
   MODULE_ANALYSIS(NAME, CREATE_PASS)
@@ -215,6 +219,9 @@ CGSCC_PASS("invalidate<all>", InvalidateAllAnalysesPass())
 CGSCC_PASS("attributor-cgscc", AttributorCGSCCPass())
 CGSCC_PASS("openmp-opt-cgscc", OpenMPOptCGSCCPass())
 CGSCC_PASS("no-op-cgscc", NoOpCGSCCPass())
+#if defined(ENABLE_ACPO)
+CGSCC_PASS("dump-feature", DumpFeaturePass())
+#endif
 #undef CGSCC_PASS
 
 #ifndef CGSCC_PASS_WITH_PARAMS
@@ -325,6 +332,9 @@ FUNCTION_PASS("view-dom", DomViewer())
 FUNCTION_PASS("view-dom-only", DomOnlyViewer())
 FUNCTION_PASS("view-post-dom", PostDomViewer())
 FUNCTION_PASS("view-post-dom-only", PostDomOnlyViewer())
+#if defined(ENABLE_ACPO)
+FUNCTION_PASS("dump-callsite", DumpCallsitePass())
+#endif
 FUNCTION_PASS("fix-irreducible", FixIrreduciblePass())
 FUNCTION_PASS("flattencfg", FlattenCFGPass())
 FUNCTION_PASS("make-guards-explicit", MakeGuardsExplicitPass())
-- 
2.38.1.windows.1

