rllvm 0.4.7

A tool to build whole-program LLVM bitcode files
Documentation

rllvm

CI codecov crates.io

Extract whole-program LLVM bitcode from any build.

Point your build system at rllvm's compiler wrappers, build normally, then pull a single .bc for the whole program back out of the finished binary.

Features

  • Drop-in C/C++ wrappers. Set CC=rllvm-cc CXX=rllvm-cxx and build, including builds that pass compiler arguments through GNU response files.
  • Rust and Cargo. Capture bitcode from the application and its wrapped dependency crates.
  • WebAssembly. Extract from linked wasm32 modules as well as objects.
  • LTO. Capture per-source bitcode under full or ThinLTO, or collect a full-LTO linker's merged module.
  • Relocatable paths. Keep extraction working when the build tree and its bitcode files move together.
  • Merge and inspect. Link modules, stage a merge by directory, or produce a bitcode archive; inspect the result with rllvm-info.

Quick start

Install rllvm:

brew install h1994st/tap/rllvm    # macOS and Linux, prebuilt
cargo install rllvm              # from source

rllvm drives an LLVM/Clang toolchain rather than bundling one, so it needs one to run:

brew install llvm                                    # macOS
sudo apt install llvm llvm-dev clang libclang-dev    # Ubuntu / Debian

On first run rllvm writes a config with tool paths discovered from llvm-config. Set RLLVM_CONFIG to use a different configuration file.

Build something and extract its bitcode:

rllvm-cc -o hello hello.c
rllvm-get-bc hello              # produces hello.bc

Or point an existing project at it:

export CC=rllvm-cc CXX=rllvm-cxx

./configure && make             # autotools
cmake -B build && cmake --build build

rllvm-get-bc build/my_program

Usage

Extracting

rllvm-get-bc hello                             # executable -> hello.bc
rllvm-get-bc libfoo.a                          # archive -> libfoo.a.bc
rllvm-get-bc --merge-strategy archive libfoo.a  # bitcode archive -> libfoo.bca
rllvm-get-bc --merge-strategy partial hello    # merge by directory, then combine
rllvm-get-bc -m hello                          # also write hello.bc.manifest
rllvm-get-bc -o out.bc hello                   # choose the output path

Outputs default to the current directory. -m writes the contributing bitcode paths to a manifest beside the input. -b remains a shorthand for archive mode. Archive extraction replaces an existing output with the current modules, so modules removed from the input do not remain in the archive.

Extraction includes the objects and archive members that carry rllvm metadata. Wrap every compilation whose code you need to capture; prebuilt native inputs do not acquire bitcode merely by being linked into a wrapped build.

Inspecting bitcode

rllvm-info hello.bc       # target, function, basic-block and instruction counts
rllvm-info -f hello.bc    # also list functions and their counts

Inspect the extracted .bc for a whole-program view. Given an object or binary, rllvm-info inspects only its first recorded module, when that file is available.

Wrapper flags

Wrapper options are long-only and prefixed --rllvm-, so they cannot collide with a compiler flag. Everything else — including -c, -v, --help and --version — goes straight to the compiler, because build systems identify the compiler by running $CC --version.

--rllvm-compiler <PATH>   Override clang/clang++ (C/C++ wrappers only)
--rllvm-verbose[=LEVEL]   Log verbosity; bare flag is 1, level 4 enables trace
--rllvm-help             Print help for the wrapper
--rllvm-version          Print the wrapper version

Place wrapper options before compiler arguments. Use = when supplying a verbosity level; diagnostics go to stderr:

rllvm-cc --rllvm-verbose=3 -pthread -c hello.c -ohello.o

Both -o hello.o and -ohello.o are accepted. A -- separator is still supported for existing shim scripts.

Response files

rllvm-cc and rllvm-cxx accept GNU-style UTF-8 compiler response files:

printf '%s\n' '-O2 -c hello.c -ohello.o' > compile.rsp
rllvm-cc @compile.rsp
rllvm-get-bc hello.o

Quoting and nested @file references follow Clang's GNU response syntax. Relative response paths resolve from the compiler's working directory. Large generated compiler and LLVM-tool commands also use response files when needed.

CMake toolchain file

cmake -B build -DCMAKE_TOOLCHAIN_FILE=path/to/rllvm/cmake/rllvm-toolchain.cmake
cmake --build build
rllvm-get-bc build/my_program

See examples/cmake/.

Rust and Cargo

RUSTC_WRAPPER=rllvm-rustc cargo build
rllvm-get-bc target/debug/my_program

Wrapped dependency crates contribute their recorded modules when their archive members reach the link. A library crate can also be extracted directly:

rllvm-get-bc 'target/debug/deps/libmylib-<hash>.rlib'

Replace <hash> with the actual artifact hash. Direct invocation also supports relative output paths:

rllvm-rustc main.rs -o app
rllvm-get-bc app -o app.bc

For direct invocation, RLLVM_REAL_RUSTC overrides rustc_filepath in the config, with rustc on PATH as the fallback. Under RUSTC_WRAPPER, Cargo supplies the compiler path. Use RLLVM_LOG_LEVEL=3 for wrapper diagnostics under Cargo; --rllvm-verbose=3, --rllvm-help, and --rllvm-version are available when invoking the wrapper yourself.

cargo check and procedural-macro crates pass through without bitcode capture. Prebuilt dependencies, including the supplied standard library, are not rebuilt by the wrapper. Use LLVM tools compatible with the LLVM version reported by rustc -vV when extracting Rust bitcode.

WebAssembly

rllvm-cc --target=wasm32-unknown-unknown -c lib.c -o lib.o
rllvm-cc --target=wasm32-unknown-unknown -c main.c -o main.o
rllvm-cc --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry \
    -o app.wasm lib.o main.o
rllvm-get-bc app.wasm -o app.bc

Linking needs wasm-ld, which ships with LLD rather than LLVM and must match your LLVM version. See examples/wasm/.

Bitcode storage and relocation

C/C++ bitcode files are hidden files beside the requested output by default. Their names distinguish the source, output, compiler, and compilation settings, so separate build variants keep separate bitcode. Set bitcode_store_path to an absolute directory to collect them centrally.

Extraction requires the recorded .bc files. Preserve them along with the objects or binaries, including when restoring outputs from a compiler cache.

By default an object records the absolute path of its bitcode, which pins it to the directory that built it. Set a root to record paths relative to it, then name the root again when extracting:

export RLLVM_BITCODE_ROOT=/path/to/build
make

# later, after the tree has moved:
rllvm-get-bc --bitcode-root /new/path/to/build prog -o prog.bc

Choose a root containing the bitcode files, including a central store if used. Absolute and relative entries can coexist; --bitcode-root resolves only the relative ones. Setting a root changes recorded paths, not where bitcode is stored.

Bitcode caching

The optional C/C++ cache reuses the extra bitcode compilation across rebuilds:

RLLVM_CACHE=1 cmake --build build   # build must already use the wrappers

Caching is off by default. RLLVM_CACHE=1 enables it and RLLVM_CACHE=0 disables it, overriding cache_enabled. The default cache directory is ~/.rllvm/cache; set cache_dir to use another location.

The native compilation still runs. Each lookup preprocesses the current inputs and checks their dependencies, command, compiler, working directory, and environment before reusing bitcode. Cache hits therefore still incur preprocessing work. If validation cannot produce a usable key, rllvm generates bitcode without caching that result.

Disable caching for changing compiler side inputs that preprocessing does not capture, such as optimization profiles. Keep compilation inputs stable while a build runs.

LTO

Set lto_mode in the config or override it with RLLVM_LTO_MODE:

Mode Captured bitcode Support
marker (default) Per-source modules, recorded in the LTO objects Full and ThinLTO; ELF and Mach-O; C and C++
save-temps The full-LTO linker's merged, post-optimization module Separate links and combined source/link invocations
skip No additional bitcode capture; emits a warning Use when capture is intentionally disabled for LTO

For a combined full-LTO build:

RLLVM_LTO_MODE=save-temps rllvm-cc -flto hello.c -o hello
rllvm-get-bc hello -o hello.bc

Use the same mode during compilation and linking. marker also supports a mix of LTO and ordinary objects; fat LTO objects record the bitcode path in both halves so either linker path can retain it.

save-temps requires actual LTO inputs: adding -flto only at the link step cannot produce a merged module from ordinary objects. ThinLTO has no single merged module to collect, so this mode warns and skips collection for ThinLTO; use marker for ThinLTO builds. Compiler queries and invocations that do not link do not collect a module. Linker temporary files explicitly requested by the user are preserved.

COFF and WebAssembly do not support marker mode; an LTO invocation there reports an error directing to skip. A save-temps link that produces no merged module is also an error.

Configuration

A TOML file, created on first run with paths inferred from llvm-config. It lives at $RLLVM_CONFIG if set, otherwise ~/.rllvm/config.toml.

Key Required Description
llvm_config_filepath Yes Absolute path to llvm-config
clang_filepath Yes Absolute path to clang
clangxx_filepath Yes Absolute path to clang++
llvm_ar_filepath Yes Absolute path to llvm-ar
llvm_link_filepath Yes Absolute path to llvm-link
llvm_objcopy_filepath No Absolute path to llvm-objcopy; preferred for embedding, with an internal fallback
rustc_filepath No Compiler for direct Rust invocation; RLLVM_REAL_RUSTC overrides; defaults to rustc on PATH
bitcode_store_path No Directory for bitcode files (must be absolute; created if missing)
bitcode_root No Record embedded paths relative to this root (default: absolute)
llvm_link_flags No Extra flags for llvm-link
lto_ldflags No Extra flags for link-time optimization
bitcode_generation_flags No Extra flags for bitcode generation (e.g. -flto)
lto_mode No How -flto builds record bitcode: marker (default), save-temps, skip; RLLVM_LTO_MODE overrides
is_configure_only No Skip extra C/C++ bitcode work (default: false)
cache_enabled No Reuse C/C++ bitcode across rebuilds; overridden by RLLVM_CACHE (default: false)
cache_dir No Cache directory (default: ~/.rllvm/cache)
log_level No 0=error (default), 1=warn, 2=info, 3=debug, 4+=trace; RLLVM_LOG_LEVEL overrides
llvm_config_filepath = '/opt/homebrew/opt/llvm/bin/llvm-config'
clang_filepath = '/opt/homebrew/opt/llvm/bin/clang'
clangxx_filepath = '/opt/homebrew/opt/llvm/bin/clang++'
llvm_ar_filepath = '/opt/homebrew/opt/llvm/bin/llvm-ar'
llvm_link_filepath = '/opt/homebrew/opt/llvm/bin/llvm-link'
log_level = 3

rllvm-init --dry-run shows a detected configuration without writing it. Use rllvm-init --llvm-prefix /path/to/llvm to choose a toolchain and -o to choose the configuration file to write.

How it works

The C/C++ wrappers run Clang normally and also emit a .bc for each captured source. Its absolute or root-relative path is written into a custom object section, newline-terminated. The linker concatenates those sections, preserving the recorded paths from the objects it includes. rllvm-get-bc reads the list, deduplicates it, and merges or archives the modules.

source.c ──► rllvm-cc ──► object file (with embedded .bc path)
                              │
                              ▼
executable ◄── linker ◄── object files
                              │
                              ▼
                        rllvm-get-bc ──► whole-program.bc

Universal (multi--arch) builds are not supported in any mode: clang cannot emit one IR file for two architectures. Build and extract one architecture at a time. rllvm-get-bc cannot read a combined universal binary.

rllvm-rustc does the same per crate. A crate that links carries the path in a marker object added to the link; a crate that produces an .rlib carries it in the archive's members, so a dependency brings its bitcode wherever it is used.

Relationship to gllvm and wllvm

rllvm started as a Rust port of gllvm (Go) and wllvm (Python), and keeps the same workflow: set CC/CXX, build, extract. It has since added WebAssembly support, a Rust wrapper, relocatable bitcode paths, and merge strategies.

The name follows the same convention: g for Go, w for whole-program-llvm, r for Rust.

rules_rllvm provides separate, Bazel-native extraction rules and does not use these binaries.

If gllvm or wllvm already work for you, there is no urgency to switch.

License

Apache-2.0