Implementing a new pass – Optimizing IR-2


NOTE
Two macros can be used to define a counter variable. If you use the STATISTIC macro, then the statistic value will only be collected in a debug build if assertions are enabled, or if LLVM_FORCE_ENABLE_STATS is set to ON on the CMake command line. If you use the ALWAYS_ENABLED_STATISTIC macro instead, then the statistic value is always collected. However, printing the statistics using the–stats command-line option only works with the former methods. If needed, you can print the collected statistics by calling the llvm::PrintStatistics(llvm::raw_ostream) function.

  1. Next, we must declare the pass class in an anonymous namespace. The class inherits from the PassInfoMixin template. This template only adds some boilerplate code, such as a name() method. It is not used to determine the type of the pass. The run() method is called by LLVM when the pass is executed. We also need a helper method called instrument():

namespace {
class PPProfilerIRPass
: public llvm::PassInfoMixin {
public:
llvm::PreservedAnalyses
run(llvm::Module &M, llvm::ModuleAnalysisManager &AM);
private:
void instrument(llvm::Function &F,
llvm::Function *EnterFn,
llvm::Function *ExitFn);
};
}

  1. Now, let’s define how a function is instrumented. Besides the function to instrument, the functions to call are passed:

void PPProfilerIRPass::instrument(llvm::Function &F,
Function *EnterFn,
Function *ExitFn) {

  1. Inside the function, we update the statistic counter: ++NumOfFunc;
  2. To easily insert IR code, we need an instance of the IRBuilder class. We will set it to the first basic block, which is the entry block of the function: IRBuilder<> Builder(&*F.getEntryBlock().begin());
  3. Now that we have the builder, we can insert a global constant that holds the name of the function we wish to instrument: GlobalVariable *FnName =
    Builder.CreateGlobalString(F.getName());
  4. Next, we will insert a call to the __ppp_enter() function, passing the name as an argument: Builder.CreateCall(EnterFn->getFunctionType(), EnterFn,
    {FnName});
  5. To call the __ppp_exit() function, we have to locate all return instructions. Conveniently, the insertion point that’s set by the calling SetInsertionPoint() function is before the instruction that’s passed as a parameter, so we can just insert the call at that point: for (BasicBlock &BB : F) {
    for (Instruction &Inst : BB) {
    if (Inst.getOpcode() == Instruction::Ret) {
    Builder.SetInsertPoint(&Inst);
    Builder.CreateCall(ExitFn->getFunctionType(),
    ExitFn, {FnName});
    }
    }
    }
    }
  6. Next, we will implement the run() method. LLVM passes in the module our pass works on and an analysis manager from which we can request analysis results if needed:

PreservedAnalyses
PPProfilerIRPass::run(Module &M,
ModuleAnalysisManager &AM) {

  1. There is a slight annoyance here: if the runtime module that contains the implementation of the __ppp_enter() and __ppp_exit() functions are instrumented, then we run into trouble because we create an infinite recursion. To avoid this, we must simply do nothing if one of those functions is defined: if (M.getFunction(“__ppp_enter”) ||
    M.getFunction(“__ppp_exit”)) {
    return PreservedAnalyses::all();
    }

Leave a Reply

Your email address will not be published. Required fields are marked *