Writing LLVM Pass in 2018 — Part III

The Missing Story —In-Tree Pass Integration

Min-Yih Hsu
4 min readAug 25, 2018

--

Just as the subtitle suggested, this article is more like a “missing guide” for LLVM (in-tree)development, rather than introducing something new.

The official Writing an LLVM Pass teach you to write a really simple (legacy) LLVM Pass that can be loaded by opt dynamically. But now you’re a lazy guy, you want to run your cool Pass automatically in opt, or even clang. This article provides one of the several ways to integrate your Pass into the legacy PassManager pipeline. My next article would then talk about how to add custom clang command line options to enable custom features.

To run a Pass in the Pass pipeline by default, we need to figure it out how the pipeline is constructed first. There is a builder class, PassManagerBuilder, used for building the legacy PassManager as well as the default Pass pipeline. The path where the builder class resides, however, is a little bit strange — It’s put in include/llvm/Transforms/IPO/PassManagerBuilder.h and lib/Transform/IPO/PassManagerBuilder.cpp, rather than IR or PassManager folders we’re familiar with.

Inside the PassManagerBuilder class, we can find that there are lots of methods self-explained by their names, for example, addInstructionCombiningPass and addFunctionSimplificationPasses. These methods can be used to add certain category of Passes to the pipeline. In addition to calling them explicitly to update the list of candidate Passes, there are many places in the code use OptLevel property, i.e. the -O1, -O2 command line options we familiar, to add Passes by their optimization levels.

Let’s look at one of the general entry points first: populateFunctionPassManager.

Before now, we only know one way to run our Pass: Register it with

static RegisterPass<MyPass> X("my-pass", ...);

and run opt with command line option -load=MyPass.so -my-pass. However, it would be strange and unnecessary to load and run a Pass dynamically every time if it is already put in the source tree.

Therefore, from the above code, we saw that if a Pass is already in the LLVM source tree, all we need to do is creating a Pass with some factory functions, createSROAPass for example, and call legacy::FunctionPassManager::add(…) explicitly to add the desired Pass into pipeline.

Sadly, it’s not over yet. It turns out that there are several trivial (which is why I wrote this article) setups before getting your in-tree Passes all set. Here is the checklist:

  1. createXXXPass function.
  2. initializeXXXPassPass function / InitializedPasses.h file.
  3. INITIALIZE_PASS_BEGIN/END/DEPENDENCY code.
  4. Put your initializeXXXPassPass in the right place.
  5. LinkAllPasses.h file.
  6. Put your createXXXPass in the right place.

The above list is my usual order to finish these tasks, but of course there is no specific priorities among them. Let’s go through them top-down.

The first item, mentioned earlier, it’s fairly straight forward, usually it only required three lines of code to implement:

FunctionPass* llvm::createMyAwesomePass() {
return new MyAwesomePass();
}

Just new an instance of your Pass and return it, period. Note that it’s a global static function in the llvm namespace, so don’t forget the prefix llvm:: or just surround it with namespace llvm {…}.

The next two checklist items, number two and three, are actually the same thing — The initializeXXXPassPass function. This function would create an internal Pass info entry for a Pass, and register it to the PassManager, alone with its required dependencies. It’s similar to RegisterPass<MyPass> X(…) for the dynamic loaded Passes. To implement this function, first we put the function declaration in include/llvm/InitializePasses.h:

// ...Previous lines...
void initializeModuleSummaryIndexWrapperPassPass(PassRegistry&);
void initializeMustExecutePrinterPass(PassRegistry&);
// -----------------------------
void initializeMyAwesomePassPass(PassRegistry&);
// -----------------------------
void initializeNameAnonGlobalLegacyPassPass(PassRegistry&);
// ...

In the source code file of our own Pass, add the following lines:

This is actually constructing the function body of initializeXXXPassPass function.

Now we have bothinitializeXXXPassPassand createXXXPass and functions. All we need to do next is putting/calling them in the right places. Let’s deal with the former one first.

There are two places we need to call initializeXXXPassPass function. The first one is inside your Pass class constructor:

MyAwesomePass() : FunctionPass(ID) {
initializeMyAwesomePassPass(*PassRegistry::getPassRegistry());
}

The second place is in the upper-level initialization function. For example, if your Pass is an analysis Pass put under lib/Analysis folder, then add the following line in lib/Analysis/Analysis.cpp :

void llvm::initializeAnalysis(PassRegistry &Registry) {
//...Other initialization function calls
initializeMyAwesomePassPass(Registry);
//...
}

If you’re a transformation Pass put under lib/Transforms/Scalar, the file you’re looking for would be lib/Transforms/Scalar/Scalar.cpp.

Now let’s move on to createXXXPass function. To prevent some aggressive link time optimizations on these createXXXPass symbols, we need to add a fake function call in include/llvm/LinkAllPasses.h :

struct ForcePassLinking {
ForcePassLinking() {
//...
(void) llvm::createMyAwesomePass();
//...
}
}

Then finally, we’re going to add a real function call on createXXXPass function inside PassManagerBuilder mentioned early in this article. You can add more complex logic to insert your Pass into pipeline under certain optimization levels or conditions, but usually I just put the function call somewhere in populateXXXPassManager , which is also mentioned earlier:

Constructing Pass pipeline is always an interesting topic in PassManager, both legacy and the new one. There are still many other factors affecting the formation of a Pass pipeline. The previous section only provide the simplest way to run your Pass by default in the legacy PassManager.

And I think that’s all for the extra efforts required to put your Pass into the LLVM source tree. Hope this makes your LLVM development easier :-)

--

--