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.
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.
Now let's start writing the source code for our analysis pass:
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;
namespace { struct CountOpcode: public FunctionPass {
std::map< std::string, int> opcodeCounter; static char ID; CountOpcode () : FunctionPass(ID) {}
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; } }; }
char CountOpcode::ID = 0;
static RegisterPass<CountOpcode> X("opcodeCounter", "Count number of opcode in a functions");
make
or cmake
command.$ 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
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.
18.224.54.168