rllvm
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-cxxand 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
wasm32modules 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:
rllvm drives an LLVM/Clang toolchain rather than bundling one, so it needs one to run:
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:
Or point an existing project at it:
&& &&
Usage
Extracting
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
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:
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:
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
See examples/cmake/.
Rust and Cargo
RUSTC_WRAPPER=rllvm-rustc
Wrapped dependency crates contribute their recorded modules when their archive members reach the link. A library crate can also be extracted directly:
Replace <hash> with the actual artifact hash. Direct invocation also supports
relative output paths:
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
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:
# later, after the tree has moved:
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
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
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 |
= '/opt/homebrew/opt/llvm/bin/llvm-config'
= '/opt/homebrew/opt/llvm/bin/clang'
= '/opt/homebrew/opt/llvm/bin/clang++'
= '/opt/homebrew/opt/llvm/bin/llvm-ar'
= '/opt/homebrew/opt/llvm/bin/llvm-link'
= 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.