Preskočite na sadržaj

Implementing an analysis and transformation pass in the LLVM compiler infrastructure

The LLVM documentation contains the Getting Started/Tutorials section, which offers two tutorials, one on implementing a language frontend and the other on building a just-in-time (JIT) compiler. While the latter tutorial covers optimization, it does so in a specific context that is not required here.

We will be following The LLVM Compiler Framework and Infrastructure Tutorial presented by Chris Lattner and Vikram Adve at the LCPC'04 Mini Workshop on Compiler Research Infrastructures in Septembed 2004. While the presentation is a bit older, the information in it is still very much relevant and a good augmentation of the available LLVM documentation.

Writing an LLVM pass

The LLVM documentation contains the guidance for writing a pass in two ways:

Since the legacy pass manager will eventually be removed from LLVM and the new pass manager is much easier to use, and it requires less boilerplate code, we will be following the latter guide.

The HelloWorldPass described in the Basic code required section is already a part of the LLVM source code and it got compiled when we initially set up the development environment. We will be modifying it from now on, but first let's use llvm-stress to create the file containing LLVM IR that will be used for testing the pass:

$ ./bin/llvm-stress -o example-stress.ll

The pass implemented in the HelloWorldPass class is registered as helloworld in the llvm/lib/Passes/PassRegistry.def file:

FUNCTION_PASS("helloworld", HelloWorldPass())

so it can be called by adding the parameter -passes with the value helloworld to opt:

$ ./bin/opt example-stress.ll -S -passes helloworld -o example-stress-opt.ll
autogen_SD0

We can observe the function name in the output. The resulting LLVM IR file can be compared to the starting file with llvm-diff and there will be no difference as the pass only performs analysis (specifically, finding functions and printing their names) and does not perform any transformation.

Assignment

Change the optimization pass so that it also prints the the number of operands for each function and the function type (signature); the API documentation of the Function class is a good place to look for a way to obtain this information.

Assignment

Change the optimization pass so that it also prints the number of times each function was called. Amend the example-stress.ll file with the function manualgen_SD0 calling the function autogen_SD0:

define void @manualgen_SD0(i8* %0, i32* %1, i64* %2, i32 %3, i64 %4, i8 %5) {
  call void (i8*, i32*, i64*, i32, i64, i8) @autogen_SD0(i8* %0, i32* %1, i64* %2, i32 %3, i64 %4, i8 %5)
  ret void
}

and use this file to verify the correct number is printed

Assignment

Change the optimization pass so that it also prints the number of usages (reads and writes) for each of the function arguments.

Assignment

Change the optimization pass so that it also prints the number of instructions and then interates over the instruction list (note that the instructions reside inside of the basic blocks). For each of the instructions, print the type and the operands, if any.

Finally, before starting with a specific project implemented using the LLVM libraries, the required reading is the LLVM Programmer’s Manual.

Author: Vedran Miletić