llvm-plugin-rs

version doc linux windows macos

This crate gives the ability to safely implement passes for the [new LLVM pass manager], by leveraging the strongly typed interface provided by [Inkwell].

If you have never developed LLVM passes before, perhaps you should take a look at this [LLVM guide] before carrying on. It will give you a simple overview of the C++ API wrapped by this crate.

If you want a deeper understanding of the many concepts surrounding the new LLVM pass manager, you should read the [official LLVM documentation].

Usage

Out-of-tree LLVM passes are plugins implemented as dynamically-linked libraries loaded by the [opt] tool. Therefore, you must add the following line in your Cargo.toml:

toml [lib] crate-type = ["cdylib"]

When importing this crate in your Cargo.toml, you will need to specify the LLVM version to use with a corresponding feature flag:

toml [dependencies] llvm-plugin = { version = "0.1", features = ["llvm10-0"] }

Supported versions:

| LLVM Version | Cargo Feature Flag | Linux | Windows | MacOS | | :----------: | :----------------: | :---------: | :---------: | :---------: | | 10.0.x | llvm10-0 | | | | | 11.0.x | llvm11-0 | | | | | 12.0.x | llvm12-0 | | | | | 13.0.x | llvm13-0 | | | | | 14.0.x | llvm14-0 | | | |

Example

A simple LLVM plugin which defines two passes, one being a transformation pass that queries the result of a second pass, an analysis one:

```rust // Define an LLVM plugin (a name and a version is required). Only cdylib crates // should define plugins, and only one definition should be done per crate.

[llvmplugin::plugin(name = "pluginname", version = "0.1")]

mod plugin { use llvmplugin::{ LlvmModuleAnalysis, LlvmModulePass, ModuleAnalysisManager, PreservedAnalyses, }; use llvmplugin::inkwell::module::Module;

// Must implement the `Default` trait.
#[derive(Default)]
struct Pass1;

// Define a transformation pass (a name is required). Such pass is allowed to
// mutate the LLVM IR. If it does, it should return `PreservedAnalysis::None`
// to notify the pass manager that all analyses are now invalidated.
#[pass(name = "pass_name")]
impl LlvmModulePass for Pass1 {
    fn run_pass(
        &self,
        module: &mut Module,
        manager: &ModuleAnalysisManager,
    ) -> PreservedAnalyses {
        // Ask the pass manager for the result of the analysis pass `Analysis1`
        // defined further below. If the result is not in cache, the pass
        // manager will call `Analysis1::run_analysis`.
        let result = manager.get_result::<Analysis1>(module);

        assert_eq!(result, "Hello World!");

        // no modification was made on the module, so the pass manager doesn't have
        // to recompute any analysis
        PreservedAnalyses::All
    }
}

// Must implement the `Default` trait.
#[derive(Default)]
struct Analysis1;

// Define an analysis pass. Such pass is not allowed to mutate the LLVM IR. It should
// be used only for inspection of the LLVM IR, and can return some result that will be
// efficiently cached by the pass manager (to prevent recomputing the same analysis
// every time its result is needed).
#[analysis]
impl LlvmModuleAnalysis for Analysis1 {
    fn run_analysis(
        &self,
        module: &Module,
        manager: &ModuleAnalysisManager,
    ) -> String {
        // .. inspect the LLVM IR of the module ..

        "Hello World!".to_owned()
    }
}

} ```

Once you compiled your custom plugin, you can use it during the compilation of C/C++ with the opt tool:

bash $ clang -c -emit-llvm main.cc -o main.bc $ opt -load-pass-plugin=libplugin.so -passes="pass_name" main.bc -o main.bc $ llc main.bc -o main.s $ clang -static main.s -o main

To learn more about how to sequentially apply more than one pass, read this [opt guide].

Linux & MacOS Requirements

Your LLVM toolchain should dynamically link the LLVM library. Fortunately, this is the case for toolchains distributed on apt and homebrew registeries.

If you are not in this case, you have to compile LLVM from sources by specifying the LLVM_LINK_LLVM_DYLIB=ON cmake flag.

Compile LLVM-14

shell $ wget https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/llvm-14.0.0.src.tar.xz $ tar xf llvm-14.0.0.src.tar.xz && cd llvm-14.0.0.src $ mkdir build && cd build $ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$HOME/llvm" \ -DLLVM_LINK_LLVM_DYLIB=ON \ -G Ninja $ ninja install

Windows Requirements

You have to compile LLVM from sources in any case, because you need to apply some patches to the LLVM code base before compiling. Then, you need to specify the LLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON cmake flag while leaving the LLVM_TARGETS_TO_BUILD flag to its default value.

Compile LLVM-14

shell $ wget https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/llvm-14.0.0.src.tar.xz $ tar xf llvm-14.0.0.src.tar.xz && cd llvm-14.0.0.src $ cat ../ci/windows/llvm-14.patch | patch -p1 $ mkdir build && cd build $ cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="C:\\llvm" \ -DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON \ -G Ninja $ ninja install $ cp lib/opt.lib /c/llvm/lib

Don't forget to update your PATH environment variable with your LLVM installation path.

Missing Features

Contributions are very welcome, make sure to check out the [Contributing Guide] first!