StaticLLVM.jl
StaticLLVM.jl provides a lightweight framework for analyzing and modifying LLVM IR generated from Julia internal functions. It enables transformations on Julia-emitted IR by identifying and replacing specific constructs—such as GC-related instructions or Julia intrinsics—with standard equivalents like malloc
, printf
, etc.
🚧 Note: This project is under active development. Many components are experimental or incomplete. Feedback and contributions are welcome!
✨ Features
- 🔧 LLVM IR Generation: Compile Julia functions to LLVM IR using Julia’s internal compiler pipeline.
- 🧠 IR Transformation Engine:
- Detect and replace specific internal Julia functions (e.g., GC calls).
- Insert detected module-level variables (globals/constants) into the IR.
- Substitute Julia intrinsics with standard C library calls (e.g.,
malloc
,printf
). - Eliminate or simplify GC-related instructions.
- 🧹 IR Cleanup and Optimization:
- Strip unnecessary metadata and simplify the IR.
- Prepare IR for external linking or embedding in native runtimes.
- 📦 Minimal Replacement Library (planned):
- Provide C-style implementations of selected Julia intrinsics for standalone use.
Installation
using StaticLLVM
using YourPackageName
build(YourPackageName.some_module)
Prerequisites
- Julia (currently tested on v1.11.5)
- Clang or compatible compiler (e.g., Intel oneAPI, AMD compiler)
Example
# ------ Example.jl ------
module Example
using StaticTools
function fib(n::Int)::Int
n <= 1 && return 1
n == 2 && return 1
return fib(n - 1) + fib(n - 2)
end
const n_global = Ref(5)
function _main_(n::Int)::Int
n += n_global[]
arr = Array{Int, 1}(undef, n)
@inbounds for i in 1:n
arr[i] = fib(i)
end
for i in eachindex(arr)
@inbounds printf("fib[%d] = %d\n", i, arr[i])
end
return 0
end
end
Example._main_(3)
# ------ make.jl ------
using StaticLLVM
# include("Example.jl") or using Example
config = StaticLLVM.get_config(;
dir = ".", # Working directory
compile_mode = :onefile, # Compile all LLVM .ll files into a single binary
clean_cache = false, # Keep intermediate cached files
debug = false, # Dump raw LLVM IR as `.debug_ll`
policy = :strip_all # Strip GC code and use `malloc` for memory allocation
clang => "clang", # Path to the clang compiler
cflag => "-O3 -g -Wall", # Flags passed to clang for optimization and warnings
)
build(Example, config)
Thus, a standalone program Example
is generated.
> Example 1
fib[1] = 1
fib[2] = 1
fib[3] = 2
fib[4] = 3
fib[5] = 5
fib[6] = 8
fib[7] = 13
In practice:
- Define your program as a Julia module.
- Use
include(...)
orusing
to load the module. - Run
build(MyModule, config)
to generate and optionally compile the LLVM IR.
The _main_()
function will be used as the LLVM program entry point.