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.
- Drop-in compiler wrappers.
export CC=rllvm-ccand build. No separator, no shim script, no build-system plugin. - Rust and cargo. Wrap
cargo buildand extract from the binary, dependency crates included. - WebAssembly. Whole-program bitcode from a linked
wasm32module, not just from individual objects. - Relocatable bitcode paths. Objects normally pin themselves to the directory that built them. Record paths relative to a root instead, and extraction keeps working after the tree moves, comes out of a container, or is replayed from a compiler cache.
- Merge strategies. Link everything into one module, stage the merge by directory for large projects, or produce a bitcode archive.
- Single static binary. No runtime to install;
cargo install rllvm. - Bitcode inspection.
rllvm-inforeports what a module contains.
Install LLVM/Clang, then rllvm:
brew install llvm # macOS
sudo apt install llvm llvm-dev clang libclang-dev # Ubuntu / Debian
cargo install rllvmBuild something and extract its bitcode:
rllvm-cc -o hello hello.c
rllvm-get-bc hello # produces hello.bcOr 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_programOn first run rllvm writes a config with tool paths discovered from llvm-config.
rllvm-get-bc hello # executable -> hello.bc
rllvm-get-bc libfoo.a # archive -> libfoo.a.bc
rllvm-get-bc -b libfoo.a # bitcode archive -> libfoo.bca
rllvm-get-bc --merge-strategy partial prog # stage the merge by directory
rllvm-get-bc -m hello # also write hello.bc.manifest
rllvm-get-bc -o out.bc helloWrapper 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 the wrapped compiler path
--rllvm-verbose[=LEVEL] Log verbosity; bare flag is level 1, max 4
--rllvm-help Print help for the wrapper
--rllvm-version Print the wrapper version
A -- separator is still accepted, so existing shim scripts keep working.
cmake -B build -DCMAKE_TOOLCHAIN_FILE=path/to/rllvm/cmake/rllvm-toolchain.cmake
cmake --build build
rllvm-get-bc build/my_programSee examples/cmake/.
RUSTC_WRAPPER=rllvm-rustc cargo build
rllvm-get-bc target/debug/my_programEvery crate in the graph contributes, so the extracted module covers dependencies as well as the binary's own code. A library crate works on its own:
rllvm-get-bc target/debug/deps/libmylib-<hash>.rlibrllvm-cc --target=wasm32-unknown-unknown -c -o lib.o lib.c
rllvm-cc --target=wasm32-unknown-unknown -nostdlib -Wl,--no-entry \
-o app.wasm lib.o main.o
rllvm-get-bc app.wasm -o app.bcLinking needs wasm-ld, which ships with LLD rather than LLVM and must match
your LLVM version. See examples/wasm/.
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.bcObjects built without a root keep absolute paths and are unaffected — the extractor tells the two apart by the leading separator, so both forms can appear in the same binary.
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 | Absolute path to rustc (default: rustc on PATH) |
bitcode_store_path |
No | Directory for intermediate bitcode files (must be absolute) |
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) |
is_configure_only |
No | Skip bitcode generation entirely (default: false) |
cache_enabled |
No | Reuse bitcode across rebuilds; also RLLVM_CACHE=1 (default: false) |
log_level |
No | 0=error (default), 1=warn, 2=info, 3=debug, 4+=trace |
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 = 3The wrappers run clang normally and, for each source, also emit a .bc. The
absolute path of that .bc is written into a custom section of the object file,
newline-terminated. The linker concatenates those sections, so the finished
binary carries a list of every translation unit that went into it.
rllvm-get-bc reads that list and links the bitcode into one module.
source.c ──► rllvm-cc ──► object file (with embedded .bc path)
│
▼
executable ◄── linker ◄── object files
│
▼
rllvm-get-bc ──► whole-program.bc
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.
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.
If gllvm or wllvm already work for you, there is no urgency to switch.