tl; dr

Run your LLVM pass with Clang automatically using the wrapper script at the end.

What’s this

This post shows how to run your LLVM pass directly using clang without having to generate LLVM bitcode and running opt.

Changes to your pass

Register your pass with for EP callback:

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
    [](PassBuilder &PB) {
         [&](llvm::ModulePassManager &mpm, llvm::PassBuilder::OptimizationLevel o) -> void{

Build your compiler pass and generate the shared binary (

Invoke clang using the following switches:

clang -fexperimental-new-pass-manager -fpass-plugin=path/to/

Wrapper script

But wait, do you have to modify the build system of your $LARGE_PROJECT? No, use this clang wrapper which automatically does this for you:

#!/usr/bin/env python

@brief  Wrapper to invoke llvm pass using clang
@author Suyash Mahar <>

from os.path import basename
import subprocess
import sys
import os

# Path to the LLVM pass
PASS_SO = '/home/smahar/git/cxlbuf/src/storeinst-pass/build/'

# Add any extra flags here. E.g., -Wl,-rpath=...

def get_bins(llvm_dir):
    cxx = os.path.join(llvm_dir, "bin/clang++")
    cc = os.path.join(llvm_dir, "bin/clang++")

    return cc, cxx

def main(fname):
    if 'LLVM_DIR' not in os.environ:
        raise RuntimeError("LLVM_DIR env not set.")

    llvm_dir = os.environ['LLVM_DIR']
    cc, cxx = get_bins(llvm_dir)

    target = cc
    # Check if invoking for clang or clang++
    if fname.endswith('++'):
        target = cxx
    args = sys.argv[1:]

    pass_args = ['-fexperimental-new-pass-manager',
                 '-fpass-plugin=' + PASS_SO] + EXTRA_FLAGS
    ret =[target] + pass_args + args)
if __name__ == "__main__":

Save this script as wclang and symlink wclang++ to wclang:

-rwxrwxr-x 1 smahar smahar 1,2K mars  19 12:03 wclang
lrwxrwxrwx 1 smahar smahar    6 mars  18 15:13 wclang++ -> wclang

And you are good to go:

CC=wclang CXX=wclang++ make