Writing an analysis pass

The analysis pass provides higher-level information about IR without actually changing the IR. The results that the analysis pass provides can be used by another analysis pass to compute its result. Also, once an analysis pass calculates the result, its result can be used several times by different passes until the IR on which this pass was run is changed. In this recipe, we will write an analysis pass that counts and outputs the number of opcodes used in a function.

Getting ready

First of all, we write the test code on which we will be running our pass:

$ cat testcode.c
int func(int a, int b){
  int sum = 0;
  int iter;
  for (iter = 0; iter < a; iter++) {
    int iter1;
    for (iter1 = 0; iter1 < b; iter1++) {
      sum += iter > iter1 ? 1 : 0;
    }
  }
  return sum;
}

Transform this into a .bc file, which we will use as the input to the analysis pass:

$ clang -c -emit-llvm testcode.c -o testcode.bc

Now create the file containing the pass source code in llvm_root_dir/lib/Transforms/opcodeCounter. Here, opcodeCounter is the directory we have created, and it is where our pass's source code will reside.

Make the necessary Makefile changes so that this pass can be compiled.

How to do it…

Now let's start writing the source code for our analysis pass:

  1. Include the necessary header files and use the llvm namespace:
    #define DEBUG_TYPE "opcodeCounter"
    #include "llvm/Pass.h"
    #include "llvm/IR/Function.h"
    #include "llvm/Support/raw_ostream.h"
    #include <map>
    using namespace llvm;
  2. Create the structure defining the pass:
    namespace {
    struct CountOpcode: public FunctionPass {
  3. Within the structure, create the necessary data structures to count the number of opcodes and to denote the pass ID of the pass:
    std::map< std::string, int> opcodeCounter;
    static char ID;
    CountOpcode () : FunctionPass(ID) {}
  4. Within the preceding structure, write the code for the actual implementation of the pass, overloading the runOnFunction function:
    virtual bool runOnFunction (Function &F) {
     llvm::outs() << "Function " << F.getName () << '
    ';
    for ( Function::iterator bb = F.begin(), e = F.end(); bb != e; ++bb) {
      for ( BasicBlock::iterator i = bb->begin(), e = bb->end(); i!= e; ++i) {
        if(opcodeCounter.find(i->getOpcodeName()) == opcodeCounter.end()) {
        opcodeCounter[i->getOpcodeName()] = 1;
        } else {
        opcodeCounter[i->getOpcodeName()] += 1;
        }
      }
    }
    
    std::map< std::string, int>::iterator i = opcodeCounter.begin();
    std::map< std::string, int>::iterator e = opcodeCounter.end();
    while (i != e) {
      llvm::outs()  << i->first << ": " << i->second << "
    ";
      i++;
    }
    llvm::outs()  << "
    ";
    opcodeCounter.clear();
    return false;
    }
    };
    }
  5. Write the code for registering the pass:
    char CountOpcode::ID = 0;
    static RegisterPass<CountOpcode> X("opcodeCounter", "Count number of opcode in a functions");
    
  6. Compile the pass using the make or cmake command.
  7. Run the pass on the test code using the opt tool to get the information on the number of opcodes present in the function:
    $ opt -load path-to-build-folder/lib/LLVMCountopcodes.so -opcodeCounter -disable-output testcode.bc
    Function func
    add: 3
    alloca: 5
    br: 8
    icmp: 3
    load: 10
    ret: 1
    select: 1
    store: 8
    

How it works…

This analysis pass works on a function level, running once for each function in the program. Hence, we have inherited the FunctionPass function when declaring the CountOpcodes : public FunctionPass struct.

The opcodeCounter function keeps a count of every opcode that has been used in the function. In the following for loops, we collect the opcodes from all the functions:

for (Function::iterator bb = F.begin(), e = F.end(); bb != e; ++bb) {
for (BasicBlock::iterator i = bb->begin(), e = bb->end(); i != e; ++i) {

The first for loop iterates over all the basic blocks present in the function, and the second for loop iterates over all the instructions present in the basic block.

The code in the first for loop is the actual code that collects the opcodes and their numbers. The code below the for loops is meant for printing the results. As we have used a map to store the result, we iterate over it to print the pair of the opcode name and its number in the function.

We return false because we are not modifying anything in the test code. The last two lines of the code are meant for registering this pass with the given name so that the opt tool can use this pass.

Finally, on execution of the test code, we get the output as different opcodes used in the function and their numbers.

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

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