├── .gitignore ├── HelloPass ├── CMakeLists.txt └── HelloPass.cpp ├── HelloLTOPass ├── CMakeLists.txt └── HelloLTOPass.cpp ├── test ├── lit.site.cfg.py.in ├── CMakeLists.txt ├── test.c ├── lto.c └── lit.cfg.py ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | *.swp 4 | -------------------------------------------------------------------------------- /HelloPass/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMHelloPass MODULE 2 | HelloPass.cpp 3 | ) 4 | -------------------------------------------------------------------------------- /HelloLTOPass/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMHelloLTOPass MODULE 2 | HelloLTOPass.cpp 3 | ) 4 | -------------------------------------------------------------------------------- /test/lit.site.cfg.py.in: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | config.llvm_tools_dir = "@LLVM_TOOLS_BINARY_DIR@" 4 | config.llvm_shlib_ext = "@LT_TEST_SHLIBEXT@" 5 | config.llvm_shlib_dir = "@CMAKE_LIBRARY_OUTPUT_DIRECTORY@" 6 | 7 | config.hello_pass_path = "@HELLO_PASS_PATH@" 8 | config.hello_lto_pass_path = "@HELLO_LTO_PASS_PATH@" 9 | 10 | import lit.llvm 11 | # lit_config is a global instance of LitConfig 12 | lit.llvm.initialize(lit_config, config) 13 | 14 | # test_exec_root: The root path where tests should be run. 15 | config.test_exec_root = os.path.join("@CMAKE_CURRENT_BINARY_DIR@") 16 | 17 | # Let the main config do the real work. 18 | lit_config.load_config(config, "@LT_TEST_SRC_DIR@/lit.cfg.py") 19 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6.0) 2 | 3 | set(HELLO_PASS_PATH $) 4 | set(HELLO_LTO_PASS_PATH $) 5 | 6 | set(LT_TEST_SHLIBEXT "${CMAKE_SHARED_LIBRARY_SUFFIX}") 7 | 8 | set(LT_TEST_SITE_CFG_INPUT "${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in") 9 | set(LT_TEST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 10 | 11 | configure_file("${LT_TEST_SITE_CFG_INPUT}" 12 | "${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py.gen" @ONLY 13 | ) 14 | file( 15 | GENERATE 16 | OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py" 17 | INPUT "${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py.gen" 18 | ) 19 | 20 | find_package(Python REQUIRED) 21 | add_custom_target(check 22 | COMMAND lit -v "${CMAKE_CURRENT_BINARY_DIR}" 23 | ) 24 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.3) 2 | 3 | set(CMAKE_CXX_STANDARD 11) 4 | 5 | if(NOT DEFINED ENV{LLVMPREFIX}) 6 | message(FATAL_ERROR "$LLVMPREFIX is not defined") 7 | else () 8 | set(ENV{LLVM_DIR} $ENV{LLVMPREFIX}/lib/cmake/llvm) 9 | endif() 10 | 11 | SET (CMAKE_C_COMPILER $ENV{LLVMPREFIX}/bin/clang) 12 | SET (CMAKE_CXX_COMPILER $ENV{LLVMPREFIX}/bin/clang++) 13 | 14 | project(llvm-passes) 15 | 16 | find_package(LLVM REQUIRED CONFIG) 17 | add_definitions(${LLVM_DEFINITIONS}) 18 | include_directories(${LLVM_INCLUDE_DIRS}) 19 | link_directories(${LLVM_LIBRARY_DIRS}) 20 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 21 | include(AddLLVM) 22 | 23 | if (${LLVM_VERSION_MAJOR} VERSION_GREATER_EQUAL 10) 24 | set(CMAKE_CXX_STANDARD 14) 25 | endif () 26 | message(STATUS "Using LLVM version ${LLVM_PACKAGE_VERSION}") 27 | 28 | add_subdirectory(HelloPass) 29 | add_subdirectory(HelloLTOPass) 30 | add_subdirectory(test) 31 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | // RUN: %clang %s -O2 -S -emit-llvm -o %t.ll 2 | // RUN: llvm-as %t.ll -o %t.bc 3 | // RUN: opt -enable-new-pm=0 -load %hello_pass_path --legacy-hello %t.ll -o %t.opt.ll \ 4 | // RUN: > %t.opt.legacypm.log 2>&1 5 | // RUN: opt -enable-new-pm=1 -load-pass-plugin %hello_pass_path --passes="hello-new-pm" %t.ll -o %t.opt.ll \ 6 | // RUN: > %t.opt.newpm.log 2>&1 7 | // 8 | // RUN: %clang %s -flegacy-pass-manager -Xclang -load -Xclang %hello_pass_path -O2 -o %t \ 9 | // RUN: > %t.clang.legacypm.log 2>&1 10 | // RUN: %clang %s -fno-legacy-pass-manager -fpass-plugin=%hello_pass_path -O2 -o %t.ll \ 11 | // RUN: > %t.clang.newpm.log 2>&1 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | int test_func(int num) { 21 | return num * 2; 22 | } 23 | 24 | int main(int argc, char *argv[]) { 25 | int num = test_func(argc); 26 | printf("hello (%d)!\n", num); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /test/lto.c: -------------------------------------------------------------------------------- 1 | // [opt] 2 | // RUN: %clang %s -O2 -S -emit-llvm -o %t.ll 3 | // RUN: llvm-as %t.ll -o %t.bc 4 | // RUN: opt -enable-new-pm=0 -load %hello_lto_pass_path --legacy-hello -hello-test %t.ll -o %t.opt.ll \ 5 | // RUN: > %t.opt.legacypm.log 2>&1 6 | // RUN: opt -enable-new-pm=1 -load-pass-plugin %hello_lto_pass_path --passes="hello-new-pm" %t.ll -o %t.opt.ll \ 7 | // RUN: > %t.opt.newpm.log 2>&1 8 | // 9 | // [clang fullLTO] 10 | // RUN: %clang %s -flegacy-pass-manager -O2 -fuse-ld=lld -flto -Wl,-mllvm=-load=%hello_lto_pass_path \ 11 | // RUN: -Wl,-mllvm=-hello-test -o %t \ 12 | // RUN: > %t.clang.full.legacypm.log 2>&1 13 | // 14 | // RUN: %clang %s -fno-legacy-pass-manager -O2 -fuse-ld=lld -flto \ 15 | // RUN: -Wl,-load-pass-plugin=%hello_lto_pass_path \ 16 | // RUN: -Wl,-mllvm=-load=%hello_lto_pass_path -o %t \ 17 | // RUN: > %t.clang.full.newpm.log 2>&1 18 | // 19 | // [clang thinLTO] 20 | // RUN: %clang %s -flegacy-pass-manager -O2 -fuse-ld=lld -flto=thin -Wl,-mllvm=-load=%hello_lto_pass_path \ 21 | // RUN: -Wl,-mllvm=-hello-test -o %t \ 22 | // RUN: > %t.clang.thin.legacypm.log 2>&1 23 | // 24 | // RUN: %clang %s -fno-legacy-pass-manager -O2 -fuse-ld=lld -flto=thin \ 25 | // RUN: -Wl,-load-pass-plugin=%hello_lto_pass_path \ 26 | // RUN: -Wl,-mllvm=-load=%hello_lto_pass_path -o %t \ 27 | // RUN: > %t.clang.thin.newpm.log 2>&1 28 | // 29 | // [lld fullLTO] 30 | // RUN: %clang %s -flto -O2 -c -o %t.o 31 | // RUN: ld.lld -plugin-opt=legacy-pass-manager -mllvm=-load=%hello_lto_pass_path -mllvm=-hello-test %t.o \ 32 | // RUN: > %t.lld.full.legacypm.log 2>&1 33 | // 34 | // RUN: ld.lld -plugin-opt=new-pass-manager -load-pass-plugin=%hello_lto_pass_path \ 35 | // RUN: -mllvm=-load=%hello_lto_pass_path %t.o \ 36 | // RUN: > %t.lld.full.newpm.log 2>&1 37 | // 38 | // [lld thinLTO] 39 | // RUN: %clang %s -flto=thin -O2 -c -o %t.o 40 | // RUN: ld.lld -plugin-opt=legacy-pass-manager -mllvm=-load=%hello_lto_pass_path -mllvm=-hello-test %t.o -o %t \ 41 | // RUN: > %t.lld.thin.legacypm.log 2>&1 42 | // RUN: ld.lld -plugin-opt=new-pass-manager -load-pass-plugin=%hello_lto_pass_path \ 43 | // RUN: -mllvm=-load=%hello_lto_pass_path %t.o \ 44 | // RUN: > %t.lld.thin.newpm.log 2>&1 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | int test_func(int num) { 54 | return num * 2; 55 | } 56 | 57 | int main(int argc, char *argv[]) { 58 | test_func(argc); 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /test/lit.cfg.py: -------------------------------------------------------------------------------- 1 | # -*- Python -*- 2 | 3 | # Configuration file for the 'lit' test runner. 4 | 5 | import platform 6 | 7 | import lit.formats 8 | # Global instance of LLVMConfig provided by lit 9 | from lit.llvm import llvm_config 10 | from lit.llvm.subst import ToolSubst 11 | 12 | from pathlib import Path 13 | 14 | # name: The name of this test suite. 15 | # (config is an instance of TestingConfig created when discovering tests) 16 | config.name = 'LLVM-TUTOR' 17 | 18 | # testFormat: The test format to use to interpret tests. 19 | # As per shtest.py (my formatting): 20 | # ShTest is a format with one file per test. This is the primary format for 21 | # regression tests (...) 22 | # I couldn't find any more documentation on this, but it seems to be exactly 23 | # what we want here. 24 | config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell) 25 | 26 | # suffixes: A list of file extensions to treat as test files. This is overriden 27 | # by individual lit.local.cfg files in the test subdirectories. 28 | config.suffixes = ['.c', '.ll'] 29 | 30 | # test_source_root: The root path where tests are located. 31 | config.test_source_root = os.path.dirname(__file__) 32 | 33 | # excludes: A list of directories to exclude from the testsuite. The 'Inputs' 34 | # subdirectories contain auxiliary inputs for various tests in their parent 35 | # directories. 36 | config.excludes = ['Inputs'] 37 | 38 | # On Mac OS, 'clang' installed via HomeBrew (or build from sources) won't know 39 | # where to look for standard headers (e.g. 'stdlib.h'). This is a workaround. 40 | if platform.system() == 'Darwin': 41 | tool_substitutions = [ 42 | ToolSubst('%clang', "clang", 43 | extra_args=["-isysroot", 44 | # http://lists.llvm.org/pipermail/cfe-dev/2016-July/049868.html 45 | "`xcrun --show-sdk-path`", 46 | # https://github.com/Homebrew/homebrew-core/issues/52461 47 | "-mlinker-version=0"]), 48 | ] 49 | else: 50 | tool_substitutions = [ 51 | ToolSubst('%clang', "clang", 52 | ) 53 | ] 54 | llvm_config.add_tool_substitutions(tool_substitutions) 55 | 56 | # The list of tools required for testing - prepend them with the path specified 57 | # during configuration (i.e. LT_LLVM_TOOLS_DIR/bin) 58 | tools = ["opt", "llvm-as", "lli", "not", "FileCheck", "clang", "ld.lld"] 59 | llvm_config.add_tool_substitutions(tools, config.llvm_tools_dir) 60 | 61 | # The LIT variable to hold the file extension for shared libraries (this is 62 | # platform dependent) 63 | config.substitutions.append(('%shlibext', config.llvm_shlib_ext)) 64 | # The LIT variable to hold the location of plugins/libraries 65 | config.substitutions.append(('%shlibdir', config.llvm_shlib_dir)) 66 | 67 | 68 | if not Path(config.hello_pass_path).is_file(): 69 | print(f"Pass not found: {hello_pass_path}") 70 | exit(1) 71 | 72 | config.substitutions.append( 73 | ("%hello_pass_path", config.hello_pass_path) 74 | ) 75 | 76 | if not Path(config.hello_lto_pass_path).is_file(): 77 | print(f"Pass not found: {hello_lto_pass_path}") 78 | exit(1) 79 | 80 | config.substitutions.append( 81 | ("%hello_lto_pass_path", config.hello_lto_pass_path) 82 | ) 83 | -------------------------------------------------------------------------------- /HelloPass/HelloPass.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // FILE: 3 | // HelloPass.cpp 4 | // https://github.com/banach-space/llvm-tutor/blob/main/HelloWorld/HelloWorld.cpp 5 | // 6 | // DESCRIPTION: 7 | // Visits all functions in a module, prints their names and the number of 8 | // arguments via stderr. Strictly speaking, this is an analysis pass (i.e. 9 | // the functions are not modified). However, in order to keep things simple 10 | // there's no 'print' method here (every analysis pass should implement it). 11 | // 12 | // USAGE: 13 | // 1. Legacy PM 14 | // opt -load libHelloWorld.dylib -legacy-hello -disable-output `\` 15 | // 16 | // 2. New PM 17 | // opt -load-pass-plugin=libHelloWorld.dylib -passes="hello" `\` 18 | // -disable-output 19 | // 20 | // 21 | // License: MIT 22 | //============================================================================= 23 | #include "llvm/IR/LegacyPassManager.h" 24 | #include "llvm/Passes/PassBuilder.h" 25 | #include "llvm/Passes/PassPlugin.h" 26 | #include "llvm/Support/raw_ostream.h" 27 | 28 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" // RegisterStandardPasses 29 | 30 | using namespace llvm; 31 | 32 | //----------------------------------------------------------------------------- 33 | // HelloPass implementation 34 | //----------------------------------------------------------------------------- 35 | // No need to expose the internals of the pass to the outside world - keep 36 | // everything in an anonymous namespace. 37 | namespace { 38 | 39 | // This method implements what the pass does 40 | void visitor(Function &F) { 41 | errs() << "Hello from: "<< F.getName() << "\n"; 42 | errs() << " number of arguments: " << F.arg_size() << "\n"; 43 | } 44 | 45 | // New PM implementation 46 | struct HelloPass : PassInfoMixin { 47 | // Main entry point, takes IR unit to run the pass on (&F) and the 48 | // corresponding pass manager (to be queried if need be) 49 | PreservedAnalyses run(Function &F, FunctionAnalysisManager &) { 50 | visitor(F); 51 | return PreservedAnalyses::all(); 52 | } 53 | }; 54 | 55 | // Legacy PM implementation 56 | struct LegacyHelloPass : public FunctionPass { 57 | static char ID; 58 | LegacyHelloPass() : FunctionPass(ID) {} 59 | // Main entry point - the name conveys what unit of IR this is to be run on. 60 | bool runOnFunction(Function &F) override { 61 | visitor(F); 62 | // Doesn't modify the input unit of IR, hence 'false' 63 | return false; 64 | } 65 | }; 66 | } // namespace 67 | 68 | //----------------------------------------------------------------------------- 69 | // New PM Registration 70 | //----------------------------------------------------------------------------- 71 | llvm::PassPluginLibraryInfo getHelloPassPluginInfo() { 72 | return {LLVM_PLUGIN_API_VERSION, "Hello", LLVM_VERSION_STRING, 73 | [](PassBuilder &PB) { 74 | PB.registerVectorizerStartEPCallback( 75 | [](llvm::FunctionPassManager &PM, OptimizationLevel Level) { 76 | PM.addPass(HelloPass()); 77 | }); 78 | PB.registerPipelineParsingCallback( 79 | [](StringRef Name, FunctionPassManager &FPM, 80 | ArrayRef) { 81 | if (Name == "hello-new-pm") { 82 | FPM.addPass(HelloPass()); 83 | return true; 84 | } 85 | return false; 86 | }); 87 | }}; 88 | } 89 | 90 | // This is the core interface for pass plugins. It guarantees that 'opt' will 91 | // be able to recognize HelloPass when added to the pass pipeline on the 92 | // command line, i.e. via '-passes=hello' 93 | extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo 94 | llvmGetPassPluginInfo() { 95 | return getHelloPassPluginInfo(); 96 | } 97 | 98 | //----------------------------------------------------------------------------- 99 | // Legacy PM Registration 100 | //----------------------------------------------------------------------------- 101 | // The address of this variable is used to uniquely identify the pass. The 102 | // actual value doesn't matter. 103 | char LegacyHelloPass::ID = 0; 104 | 105 | // This is the core interface for pass plugins. It guarantees that 'opt' will 106 | // recognize LegacyHelloPass when added to the pass pipeline on the command 107 | // line, i.e. via '--legacy-hello' 108 | static RegisterPass 109 | X("legacy-hello", "Hello Pass", 110 | true, // This pass doesn't modify the CFG => true 111 | false // This pass is not a pure analysis pass => false 112 | ); 113 | 114 | static RegisterStandardPasses Y( 115 | PassManagerBuilder::EP_OptimizerLast, 116 | [](const PassManagerBuilder &Builder, 117 | legacy::PassManagerBase &PM) { PM.add(new LegacyHelloPass()); }); 118 | -------------------------------------------------------------------------------- /HelloLTOPass/HelloLTOPass.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // FILE: 3 | // HelloPass.cpp 4 | // https://github.com/banach-space/llvm-tutor/blob/main/HelloWorld/HelloWorld.cpp 5 | // 6 | // DESCRIPTION: 7 | // Visits all functions in a module, prints their names and the number of 8 | // arguments via stderr. Strictly speaking, this is an analysis pass (i.e. 9 | // the functions are not modified). However, in order to keep things simple 10 | // there's no 'print' method here (every analysis pass should implement it). 11 | // 12 | // USAGE: 13 | // 1. Legacy PM 14 | // opt -load libHelloWorld.dylib -legacy-hello -disable-output `\` 15 | // 16 | // 2. New PM 17 | // opt -load-pass-plugin=libHelloWorld.dylib -passes="hello" `\` 18 | // -disable-output 19 | // 20 | // 21 | // License: MIT 22 | //============================================================================= 23 | #include "llvm/IR/LegacyPassManager.h" 24 | #include "llvm/Passes/PassBuilder.h" 25 | #include "llvm/Passes/PassPlugin.h" 26 | #include "llvm/Support/raw_ostream.h" 27 | 28 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" // RegisterStandardPasses 29 | #include "llvm/Support/CommandLine.h" // comand line arguments 30 | 31 | using namespace llvm; 32 | 33 | static cl::opt ClTestArgument( 34 | "hello-test", 35 | cl::desc("If set will print extra message"), 36 | cl::init(false)); 37 | 38 | //----------------------------------------------------------------------------- 39 | // HelloPass implementation 40 | //----------------------------------------------------------------------------- 41 | // No need to expose the internals of the pass to the outside world - keep 42 | // everything in an anonymous namespace. 43 | namespace { 44 | 45 | // This method implements what the pass does 46 | void visitor(Function &F) { 47 | errs() << "Hello from: "<< F.getName() << "\n"; 48 | errs() << " number of arguments: " << F.arg_size() << "\n"; 49 | if (ClTestArgument) 50 | errs() << "ClTestArgument is enabled!\n"; 51 | } 52 | 53 | // New PM implementation 54 | struct HelloPass : PassInfoMixin { 55 | // Main entry point, takes IR unit to run the pass on (&F) and the 56 | // corresponding pass manager (to be queried if need be) 57 | PreservedAnalyses run(Function &F, FunctionAnalysisManager &) { 58 | errs() << "I'm a function!\n"; 59 | visitor(F); 60 | return PreservedAnalyses::all(); 61 | } 62 | PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { 63 | errs() << "I'm a module!\n"; 64 | return PreservedAnalyses::all(); 65 | } 66 | }; 67 | 68 | // Legacy PM implementation 69 | struct LegacyHelloPass : public FunctionPass { 70 | static char ID; 71 | LegacyHelloPass() : FunctionPass(ID) {} 72 | // Main entry point - the name conveys what unit of IR this is to be run on. 73 | bool runOnFunction(Function &F) override { 74 | errs() << "Legacy\n"; 75 | visitor(F); 76 | // Doesn't modify the input unit of IR, hence 'false' 77 | return false; 78 | } 79 | }; 80 | } // namespace 81 | 82 | //----------------------------------------------------------------------------- 83 | // New PM Registration 84 | //----------------------------------------------------------------------------- 85 | llvm::PassPluginLibraryInfo getHelloPassPluginInfo() { 86 | return {LLVM_PLUGIN_API_VERSION, "Hello", LLVM_VERSION_STRING, 87 | [](PassBuilder &PB) { 88 | PB.registerFullLinkTimeOptimizationEarlyEPCallback( 89 | [](ModulePassManager &PM, OptimizationLevel Level) { 90 | PM.addPass(HelloPass()); 91 | }); 92 | /* runs at link time with thinLTO */ 93 | PB.registerVectorizerStartEPCallback( 94 | [](FunctionPassManager &PM, OptimizationLevel Level) { 95 | PM.addPass(HelloPass()); 96 | }); 97 | PB.registerPipelineParsingCallback( 98 | [](StringRef Name, llvm::FunctionPassManager &PM, 99 | ArrayRef) { 100 | if (Name == "hello-new-pm") { 101 | PM.addPass(HelloPass()); 102 | return true; 103 | } 104 | return false; 105 | }); 106 | }}; 107 | } 108 | 109 | // This is the core interface for pass plugins. It guarantees that 'opt' will 110 | // be able to recognize HelloPass when added to the pass pipeline on the 111 | // command line, i.e. via '-passes=hello' 112 | extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo 113 | llvmGetPassPluginInfo() { 114 | return getHelloPassPluginInfo(); 115 | } 116 | 117 | //----------------------------------------------------------------------------- 118 | // Legacy PM Registration 119 | //----------------------------------------------------------------------------- 120 | // The address of this variable is used to uniquely identify the pass. The 121 | // actual value doesn't matter. 122 | char LegacyHelloPass::ID = 0; 123 | 124 | // This is the core interface for pass plugins. It guarantees that 'opt' will 125 | // recognize LegacyHelloPass when added to the pass pipeline on the command 126 | // line, i.e. via '--legacy-hello' 127 | static RegisterPass 128 | X("legacy-hello", "Hello Pass", 129 | true, // This pass doesn't modify the CFG => true 130 | false // This pass is not a pure analysis pass => false 131 | ); 132 | 133 | // static RegisterStandardPasses Y( 134 | // PassManagerBuilder::EP_OptimizerLast, 135 | // [](const PassManagerBuilder &Builder, 136 | // legacy::PassManagerBase &PM) { PM.add(new LegacyHelloPass()); }); 137 | 138 | static llvm::RegisterStandardPasses RegisterHelloLTOThinPass( 139 | llvm::PassManagerBuilder::EP_ModuleOptimizerEarly, 140 | [](const llvm::PassManagerBuilder &Builder, 141 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyHelloPass()); }); 142 | 143 | static llvm::RegisterStandardPasses RegisterHelloLTOPass( 144 | llvm::PassManagerBuilder::EP_FullLinkTimeOptimizationEarly, 145 | [](const llvm::PassManagerBuilder &Builder, 146 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyHelloPass()); }); 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to use LLVM passes 2 | 3 | ## How to run 4 | 5 | ``` 6 | mkdir build 7 | cd build 8 | cmake .. 9 | make 10 | cd .. 11 | # lit should also work 12 | llvm-lit build/test 13 | ``` 14 | 15 | This README is mostly just a braindump that might or might not be useful for anybody. 16 | No guarantees for correct information ;) 17 | 18 | ## Legacy vs New Pass Manager 19 | 20 | If in doubt, save yourself some trouble and use the old pass manager. 21 | 22 | With the new pass manager until recently callbacks for LTO were missing 23 | and running passes with `lld` with the new pass manager is not yet working. 24 | 25 | ## Possible Entry Points 26 | 27 | ### Legacy Pass Manager 28 | 29 | Extract from `llvm/include/llvm/Transforms/IPO/PassManagerBuilder.h`: 30 | 31 | | Entry Point | Description | 32 | | -------------------------------- | ------------------------------------------------------------------------------------- | 33 | | EP_EarlyAsPossible | allows adding passes before any other transformations | 34 | | EP_ModuleOptimizerEarly | allows adding passes just before the main module-level optimization passes | 35 | | EP_OptimizerLast | allows adding passes that run after everything else | 36 | | EP_VectorizerStart | allows adding optimization passes before the vectorizer and other highly target specific optimization passes are executed | 37 | | EP_EnabledOnOptLevel0 | allows adding passes that should not be disabled by O0 optimization level | 38 | | EP_FullLinkTimeOptimizationEarly | allows adding passes that run at Link Time, before Full Link Time Optimization | 39 | | EP_FullLinkTimeOptimizationLast | allows adding passes that run at Link Time, after Full Link Time Optimization | 40 | 41 | ### New Pass Manager 42 | 43 | Extract from `llvm/include/llvm/Passes/PassBuilder.h`: 44 | 45 | | Entry Point | Description | 46 | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------ | 47 | | registerPipelineParsingCallback | for `opt` | 48 | | registerOptimizerLastEPCallback | adding optimizations at the very end of the function optimization pipeline | 49 | | registerPipelineStartEPCallback | adding optimization once at the start of the pipeline | 50 | | registerVectorizerStartEPCallback | adding optimization passes before the vectorizer and other highly target specific optimization passes are executed | 51 | 52 | ## Pipelines 53 | 54 | There are several pipelines, the chosen pipeline depends on e.g. if LTO is used or the optimization level. 55 | 56 | ### Legacy Pass Manager 57 | 58 | Defined in `llvm/lib/Transforms/IPO/PassManagerBuilder.cpp`: 59 | 60 | | Optimizaton Level 0 | Default | thinLTO | LTO | 61 | | --------------------- | ------------------------ | ------------------------ | -------------------------------- | 62 | | EP_EnabledOnOptLevel0 | EP_ModuleOptimizerEarly | EP_ModuleOptimizerEarly | EP_FullLinkTimeOptimizationEarly | 63 | | | EP_CGSCCOptimizerLate | EP_CGSCCOptimizerLate | EP_FullLinkTimeOptimizationLast | 64 | | | EP_LateLoopOptimizations | EP_LateLoopOptimizations | | 65 | | | EP_LoopOptimizerEnd | EP_LoopOptimizerEnd | | 66 | | | EP_ScalarOptimizerLate | EP_ScalarOptimizerLate | | 67 | | | EP_VectorizerStart | EP_VectorizerStart | | 68 | | | EP_OptimizerLast | EP_OptimizerLast | | 69 | 70 | ### New Pass Manager 71 | 72 | Defined in `llvm/lib/Passes/PassBuilderPipelines.cpp`: 73 | 74 | #### External 75 | 76 | without LTO: 77 | 78 | | Optimizaton Level 0 (buildO0DefaultPipeline) | Default (buildPerModuleDefaultPipeline) | 79 | | -------------------------------------- | ----------------------------------------------------------- | 80 | | PipelineStartEPCallbacks | PipelineStartEPCallbacks | 81 | | PipelineEarlySimplificationEPCallbacks | PipelineEarlySimplificationEPCallbacks | 82 | | CGSCCOptimizerLateEPCallbacks | CGSCCOptimizerLateEPCallbacks (if `EnableModuleInliner`) | 83 | | LateLoopOptimizationsEPCallbacks | LateLoopOptimizationsEPCallbacks | 84 | | LoopOptimizerEndEPCallbacks | LoopOptimizerEndEPCallbacks | 85 | | ScalarOptimizerLateEPCallbacks | ScalarOptimizerLateEPCallbacks | 86 | | VectorizerStartEPCallbacks | VectorizerStartEPCallbacks | 87 | | OptimizerLastEPCallbacks | OptimizerLastEPCallbacks | 88 | 89 | with LTO (unsure right now): 90 | 91 | | buildThinLTOPreLinkDefaultPipeline (compile time) | buildThinLTODefaultPipeline (link time) | buildLTOPreLinkDefaultPipeline (compile time) | buildLTODefaultPipeline (link time) | 92 | | -------------------------------------- | --------------------------------------- | -------------------------------------- | - | 93 | | PipelineStartEPCallbacks | | PipelineStartEPCallbacks | FullLinkTimeOptimizationEarlyEPCallbacks (added recently) | 94 | | PipelineEarlySimplificationEPCallbacks | PipelineEarlySimplificationEPCallbacks | PipelineEarlySimplificationEPCallbacks | FullLinkTimeOptimizationLastEPCallbacks (added recently) | 95 | | CGSCCOptimizerLateEPCallbacks | CGSCCOptimizerLateEPCallbacks | CGSCCOptimizerLateEPCallbacks | | 96 | | LateLoopOptimizationsEPCallbacks | LateLoopOptimizationsEPCallbacks | LateLoopOptimizationsEPCallbacks | | 97 | | LoopOptimizerEndEPCallbacks | LoopOptimizerEndEPCallbacks | LoopOptimizerEndEPCallbacks | | 98 | | ScalarOptimizerLateEPCallbacks | ScalarOptimizerLateEPCallbacks | ScalarOptimizerLateEPCallbacks | | 99 | | | VectorizerStartEPCallbacks | VectorizerStartEPCallbacks | | 100 | | OptimizerLastEPCallbacks | OptimizerLastEPCallbacks | OptimizerLastEPCallbacks | | 101 | 102 | 103 | #### Internal (used by other pipelines) 104 | 105 | `buildO1FunctionSimplificationPipeline`: 106 | * `LateLoopOptimizationsEPCallbacks` 107 | * `LoopOptimizerEndEPCallbacks` 108 | * `ScalarOptimizerLateEPCallbacks` 109 | 110 | `buildFunctionSimplificationPipeline`: 111 | * call `buildO1FunctionSimplificationPipeline` (only with `-O1`) 112 | * `LateLoopOptimizationsEPCallbacks` 113 | * `LoopOptimizerEndEPCallbacks` 114 | * `ScalarOptimizerLateEPCallbacks` 115 | 116 | `buildInlinerPipeline`: 117 | * `CGSCCOptimizerLateEPCallbacks` 118 | * call `buildFunctionSimplificationPipeline` 119 | 120 | `buildModuleInlinerPipeline`: 121 | * call `buildFunctionSimplificationPipeline` 122 | 123 | `buildModuleSimplificationPipeline`: 124 | * `PipelineEarlySimplificationEPCallbacks` 125 | * call `buildModuleInlinerPipeline` if `EnableModuleInliner` else `buildInlinerPipeline` 126 | 127 | `buildModuleOptimizationPipeline`: 128 | * `VectorizerStartEPCallbacks` 129 | * `OptimizerLastEPCallbacks` 130 | 131 | ## Legacy Pass Manager Callbacks 132 | 133 | (feel free to skip, this is just to find the correct functions again when debugging) 134 | 135 | Loading Passes: 136 | RegisterPass (used with `opt`): 137 | - Pass goes into PassRegistry 138 | - through listener the cli option is registered (in `passRegistered`) 139 | 140 | RegisterStandardPasses (used with `clang`/`lld`): 141 | - calls `addGlobalExtension` 142 | - `addExtensionsToPM` adds `GlobalExtension` passes for the correct extension point 143 | 144 | Running Passes: 145 | - to enable use (`--legacy-hello`): 146 | - loading the pass enabled that cli option: 147 | - setting it will trigger `handleOccurrence` (in `Commandline.h`) which sets pass as a command line option 148 | - within `opt` this is then located in `PassList` 149 | 150 | ## New Pass Manager Callbacks 151 | 152 | (feel free to skip, this is just to find the correct functions again when debugging) 153 | 154 | Loading Passes: 155 | - `-load-pass-plugin` loads passes into `PassPlugins` (with `opt`) 156 | - `PassPlugin->registerPassBuilderCallbacks(PB);` in `runPassPipeline` registers callbacks to register the pass 157 | 158 | - `fpass-plugin` loads pass (with `clang`) directly into `PassPlugins` 159 | - in `EmitAssemblyWithNewPassManager` the `PassPlugins` get loaded and `registerPassBuilderCallbacks` is called 160 | 161 | Running Passes with `opt`: 162 | - registerPipelineParsingCallback 163 | - `PassPipeline` is passed in through `-passes=""` 164 | - runs within `parsePassPipeline` (called by `runPassPipeline`) (no control where in the pipeline the pass is inserted?) 165 | 166 | ## LTO: Full vs Thin 167 | ``` 168 | clang -fuse-ld=lld -flto file1.c file2.c -o a.out 169 | clang -fuse-ld=lld -flto=thin file1.c file2.c -o a.out 170 | ``` 171 | 172 | With full LTO you have visiblity over the entire linked blob (both `file1.c` and `file2.c`) and apply passes single-threaded. 173 | With thin LTO you run passes in parallel on `file1.c` and `file2.c` and apply passes on both multi-threaded. 174 | 175 | ## How to apply passes 176 | 177 | Applying passes with `opt` will just do that, you register them in the pipeline and specify the name of passes to run. 178 | Applying passes with `clang` or `lld` (with LTO) requires you to set the extension point on where the pass should run. 179 | 180 | With the legacy pass manager with `clang` and `lld` passes get run automatically without an additional command line argument. With `opt` you need to supply a commmand line argument to run the pass. 181 | 182 | Applying passes is different for pretty much all the three possible options (`clang`, `opt`, `lld`) and also differs between the new and old pass manager. 183 | To make this a bit more clear, this repo should provide some examples on how to do it. 184 | 185 | ## Miscellaneous notes 186 | 187 | [llvm-tutor](https://github.com/banach-space/llvm-tutor/) is a great resource for writing out-of-tree LLVM passes and much of this was inspired by that work. 188 | 189 | If you use the new pass manager with `lld` you need to also include `-mllvm=-load=` to load any additional CLI arguments your pass is exposing. 190 | (It's not working anyways currently but hopefully soon if https://reviews.llvm.org/D120490 is merged). 191 | 192 | New PM extension points for the LTO pipeline also only got merged very recently (https://github.com/llvm/llvm-project/commit/942efa5927ae7514d8e03d90e67284945b7c40fe) 193 | 194 | The code in this repo was tested on llvm-project (`374bb6dd8090ffaca5aad86b2d1cd9acad401f3a`). 195 | 196 | the `-fpass-plugin` option of `clang` is only to run compile time passes (even with LTO). 197 | 198 | the `--lto-newpm-passes` option of `lld` will only run the specific passes, not any default pipeline (AFAIK). 199 | --------------------------------------------------------------------------------