Writing an inlining transformation pass

As we know, by inlining we mean expanding the function body of the function called at the call site, as it may prove useful through faster execution of code. The compiler takes the decision whether to inline a function or not. In this recipe, you will learn to how to write a simple function-inlining pass that makes use of the implementation in LLVM for inlining. We will write a pass that will handle the functions marked with the alwaysinline attribute.

Getting ready

Let's write a test code that we will run our pass on. Make the necessary changes in the lib/Transforms/IPO/IPO.cpp and include/llvm/InitializePasses.h files, the include/llvm/Transforms/IPO.h file, and the /include/llvm-c/Transforms/IPO.h file to include the following pass. Also make the necessary makefile changes to include his pass:

$ cat testcode.c
define i32 @inner1() alwaysinline {
  ret i32 1
}
define i32 @outer1() {
  %r = call i32 @inner1()
  ret i32 %r
}

How to do it…

We will now write the code for the pass:

  1. Include the necessary header files:
    #include "llvm/Transforms/IPO.h"
    #include "llvm/ADT/SmallPtrSet.h"
    #include "llvm/Analysis/AliasAnalysis.h"
    #include "llvm/Analysis/AssumptionCache.h"
    #include "llvm/Analysis/CallGraph.h"
    #include "llvm/Analysis/InlineCost.h"
    #include "llvm/IR/CallSite.h"
    #include "llvm/IR/CallingConv.h"
    #include "llvm/IR/DataLayout.h"
    #include "llvm/IR/Instructions.h"
    #include "llvm/IR/IntrinsicInst.h"
    #include "llvm/IR/Module.h"
    #include "llvm/IR/Type.h"
    #include "llvm/Transforms/IPO/InlinerPass.h"
    
    using namespace llvm;
    
  2. Describe the class for our pass:
    namespace {
    
    class MyInliner : public Inliner {
      InlineCostAnalysis *ICA;
    
    public:
        MyInliner() : Inliner(ID, -2000000000,
    /*InsertLifetime*/ true),
                        ICA(nullptr) {
        initializeMyInlinerPass(*PassRegistry::getPassRegistry());
      }
    
      MyInliner(bool InsertLifetime)
          : Inliner(ID, -2000000000, InsertLifetime), ICA(nullptr) {
        initializeMyInlinerPass(*PassRegistry::getPassRegistry());
      }
    
      static char ID;
    
      InlineCost getInlineCost(CallSite CS) override;
    
      void getAnalysisUsage(AnalysisUsage &AU) const override;
      bool runOnSCC(CallGraphSCC &SCC) override;
    
      using llvm::Pass::doFinalization;
      bool doFinalization(CallGraph &CG) override {
        return removeDeadFunctions(CG, /*AlwaysInlineOnly=*/ true);
      }
    };
    }
    
  3. Initialize the pass and add the dependencies:
    char MyInliner::ID = 0;
    INITIALIZE_PASS_BEGIN(MyInliner, "my-inline",
                    "Inliner for always_inline functions", false, false)
    INITIALIZE_AG_DEPENDENCY(AliasAnalysis)
    INITIALIZE_PASS_DEPENDENCY(AssumptionTracker)
    INITIALIZE_PASS_DEPENDENCY(CallGraphWrapperPass)
    INITIALIZE_PASS_DEPENDENCY(InlineCostAnalysis)
    INITIALIZE_PASS_END(MyInliner, "my-inline",
                    "Inliner for always_inline functions", false, false)
    
    Pass *llvm::createMyInlinerPass() { return new MyInliner(); }
    
    Pass *llvm::createMynlinerPass(bool InsertLifetime) {
      return new MyInliner(InsertLifetime);
    }
    
  4. Implement the function to get the inlining cost:
    InlineCost MyInliner::getInlineCost(CallSite CS) {
      Function *Callee = CS.getCalledFunction();
    if (Callee && !Callee->isDeclaration() &&
          CS.hasFnAttr(Attribute::AlwaysInline) &&
          ICA->isInlineViable(*Callee))
        return InlineCost::getAlways();
    
      return InlineCost::getNever();
    }
    
  5. Write the other helper methods:
    bool MyInliner::runOnSCC(CallGraphSCC &SCC) {
      ICA = &getAnalysis<InlineCostAnalysis>();
      return Inliner::runOnSCC(SCC);
    }
    
    void MyInliner::getAnalysisUsage(AnalysisUsage &AU) const {
      AU.addRequired<InlineCostAnalysis>();
      Inliner::getAnalysisUsage(AU);
    }
    
  6. Compile the pass. After compiling, run it on the preceding test case:
    $ opt -inline-threshold=0 -always-inline -S test.ll
    
    ; ModuleID = 'test.ll'
    
    ; Function Attrs: alwaysinline
    define i32 @inner1() #0 {
      ret i32 1
    }
    define i32 @outer1() {
      ret i32 1
    }
    

How it works...

This pass that we have written will work for the functions with the alwaysinline attribute. The pass will always inline such functions.

The main function at work here is InlineCost getInlineCost(CallSite CS). This is a function in the inliner.cpp file, which needs to be overridden here. So, on the basis of the inlining cost calculated here, we decide whether or not to inline a function. The actual implementation, on how the inlining process works, is in the inliner.cpp file.

In this case, we return InlineCost::getAlways(); for the functions marked with the alwaysinline attribute. For the others, we return InlineCost::getNever(). In this way, we can implement inlining for this simple case. If you want to dig deeper and try other variations of inlining—and learn how to make decisions about inlining—you can check out the inlining.cpp file.

When this pass is run over the test code, we see that the call of the inner1 function is replaced by its actual function body.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.42.243