rllvm
Compiler wrappers for building whole-program LLVM bitcode files — a Rust port of gllvm/wllvm.
How It Works
rllvm provides drop-in compiler wrappers (rllvm-cc, rllvm-cxx) that transparently run clang/clang++ and simultaneously generate LLVM bitcode. The bitcode file paths are embedded into a special section of each object file. A separate tool (rllvm-get-bc) then reads those paths and links all the bitcode into a single whole-program .bc file.
source.c ──► rllvm-cc ──► object file (with embedded .bc path)
│
▼
executable ◄── linker ◄── object files
│
▼
rllvm-get-bc ──► whole-program.bc
Installation
Prerequisites
LLVM/Clang must be installed:
# macOS
# Ubuntu / Debian
From crates.io
From source
Usage
Compile a single file
# Compile C code (wraps clang)
# Compile C++ code (wraps clang++)
Arguments before -- are rllvm flags; arguments after -- are passed directly to the underlying compiler.
Extract bitcode
# Extract linked bitcode from an executable
# Extract from a static library
# Build a bitcode archive instead of linking
# Save a manifest of individual bitcode file paths
# Specify output path
Build a real project
Use CC and CXX environment variables to inject rllvm into any build system:
# Autotools
&&
# CMake
&&
# Extract bitcode from the final binary
CMake toolchain file
rllvm ships a CMake toolchain file for a more integrated approach:
# Extract bitcode
See examples/cmake/ for a complete example.
Wrapper flags
rllvm-cc [OPTIONS] <compiler args...>
Usable directly as CC -- no `--` separator required:
export CC=rllvm-cc && ./configure && make
Wrapper options are long-only and prefixed `--rllvm-`, so they cannot collide
with a compiler flag. Everything else, including `-c`, `-v`, `--help` and
`--version`, is passed straight to the compiler -- build systems identify the
compiler by running `$CC --version`, so the wrapper must not answer it.
Options:
--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
`--` is still accepted, so existing shim scripts keep working:
rllvm-cc --rllvm-verbose=3 -- -o hello hello.c
Configuration
rllvm is configured via a TOML file. On first run, a default config is created at ~/.rllvm/config.toml with tool paths inferred from llvm-config.
Config file location
Set the RLLVM_CONFIG environment variable to use a custom path:
Config options
| 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 (recorded but currently unused) |
bitcode_store_path |
No | Directory for intermediate bitcode files (must be absolute) |
bitcode_root |
No | Record embedded bitcode paths relative to this root, so objects survive being moved (default: absolute paths) |
llvm_link_flags |
No | Extra flags passed to 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) |
log_level |
No | 0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace |
Relocatable bitcode paths
By default an object records the absolute path of its bitcode, which pins it to the directory that built it. That breaks if the tree is moved, copied out of a container, replayed from a compiler cache into a different tree, or handed to another CI job.
Set a root to record paths relative to it, then name the root again when extracting:
# later, after the tree has moved:
RLLVM_BITCODE_ROOT overrides the bitcode_root config key. Objects 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.
Example config
= '/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'
= '/opt/homebrew/opt/llvm/bin/llvm-objcopy'
= '/tmp/bitcode_store'
= 3
Why rllvm?
rllvm is a Rust rewrite of gllvm (Go) and wllvm (Python). All three tools solve the same problem — extracting whole-program LLVM bitcode — but rllvm offers:
- Single static binary — no Go or Python runtime needed;
cargo installand go. - Cross-platform — tested on Linux and macOS in CI.
- Drop-in compatible — same workflow as gllvm/wllvm: set
CC/CXX, build, extract. - TOML configuration — auto-generated config file with LLVM tool paths discovered from
llvm-config.
If you're already using gllvm or wllvm and they work for you, there's no urgency to switch. rllvm is a good fit if you prefer a self-contained Rust binary or want to integrate with a Rust-based toolchain.