LLVMParty/llvm-nanobind
GitHub: LLVMParty/llvm-nanobind
Stars: 21 | Forks: 2
# llvm-nanobind
Python bindings for the LLVM-C API using [nanobind](https://github.com/wjakob/nanobind).
This project provides a Pythonic interface to LLVM's compiler infrastructure, enabling you to build compilers, analyzers, and code transformation tools in Python.
**Current status**: Experimental but usable. The bindings target LLVM 21.1.6 and are published as `llvm-nanobind` on PyPI (import name: `llvm`). Wheels bundle LLVM and are built/tested in CI for Linux x86_64/aarch64, macOS arm64, and Windows x86_64. The API is still evolving; expect breaking changes until a stable release.
Version numbers track LLVM: `21.1.6.1` means LLVM `21.1.6` plus binding/package revision `1`.
_Note_: This project is 90%+ vibe coded. It is mostly an experiment to see what LLMs can do when you set things up properly.
## Installation
Released wheels bundle LLVM for supported platforms, so normal installation is:
pip install llvm-nanobind
python -c "import llvm; print(llvm)"
For source/development builds, see [Development](#development).
See [llvm-nanobind-example](https://github.com/LLVMParty/llvm-nanobind-example) for a simple example project.
## Quick Start
import llvm
# Create a simple function that returns 42.
with llvm.create_context() as ctx:
i32 = ctx.types.i32
fn_type = ctx.types.function(i32, [])
with ctx.create_module("example") as mod:
fn = mod.add_function("get_answer", fn_type)
entry = fn.append_basic_block("entry")
with entry.create_builder() as builder:
builder.ret(i32.constant(42))
assert mod.verify(), mod.verification_error
print(mod)
This exact snippet is available as [`examples/quick_start.py`](examples/quick_start.py).
## Examples
Runnable examples live in [`examples/`](examples/) and are covered by `tests/test_examples.py`.
uv run python examples/quick_start.py
uv run python examples/transform_replace_add.py
More examples worth browsing:
- [`examples/intrinsic_memcpy.py`](examples/intrinsic_memcpy.py) - call an LLVM intrinsic by name with `Builder.intrinsic(...)`
- [`examples/optimize_module.py`](examples/optimize_module.py) - optimize a module with a PassBuilder pipeline string
- [`examples/optimize_function.py`](examples/optimize_function.py) - optimize one function with a function-level PassBuilder pipeline string
- [`examples/emit_object_assembly.py`](examples/emit_object_assembly.py) - emit host object code and assembly from a module
- [`examples/jit_add.py`](examples/jit_add.py) - JIT-compile IR, call it through ctypes, and register a Python callback
- [`examples/instruction_metadata.py`](examples/instruction_metadata.py) - attach custom metadata to an instruction and print the IR
- [`examples/named_metadata.py`](examples/named_metadata.py) - create module named metadata and print the IR
- [`examples/metadata_debug_info.py`](examples/metadata_debug_info.py) - attach metadata, create debug info, and use debug-location scopes
- [`examples/transform_replace_add.py`](examples/transform_replace_add.py) - simple IR transformation using operands, RAUW, and instruction deletion
- [`examples/bc-stats.py`](examples/bc-stats.py) - print per-function instruction histograms from LLVM IR
- [`examples/bc-graphviz.py`](examples/bc-graphviz.py) - emit a GraphViz-style control-flow graph from LLVM IR
- [`examples/bc-profile.py`](examples/bc-profile.py) - instrument LLVM IR with profiling hooks
## Current capabilities
- Pythonic wrappers for contexts, modules, types, values, functions, basic blocks, builders, metadata, debug info, object files, targets, target machines, and pass builder options
- IR construction, traversal, and transformation helpers: constants, globals, PHI/control flow/memory/cast/cmp instructions, operands, predecessors, RAUW, split blocks, move/clone/erase instructions
- IR and bitcode parsing/writing, lazy bitcode modules, module cloning/linking, diagnostics, attributes, COMDATs, calling conventions, linkage/visibility/storage controls
- Metadata/debug-info APIs including named metadata views, module flag views, instruction/global metadata mappings, DIBuilder recipes, and debug-location scopes
- Target lookup, data layouts, `Module.emit_object()`, `Module.emit_assembly()`, target-machine emission, and PassBuilder pipeline execution via `Module.optimize()`, `Function.optimize()`, and `Module.run_passes()`
- Generic intrinsic calls with `Builder.intrinsic(...)` and in-process JIT execution through the LLVM-C ORC LLJIT API
- Lifetime/validity guards that turn many use-after-free, disposed-object, null-reference, and wrong-kind mistakes into Python exceptions instead of hard crashes
- Auto-generated typed `.pyi` stubs for IDEs/type checkers
- Golden-master tests, Python regression scripts, examples, and vendored `llvm-c-test` lit tests, including CI coverage against installed wheels
## Documentation
Type stubs are auto-generated and provide IDE intellisense. After building, find them at:
.venv/lib/python3.*/site-packages/llvm/__init__.pyi
For development documentation, see `devdocs/README.md`.
## Known limitations
- The API is not stable yet; method names and ownership rules may still change.
- Scope follows the LLVM-C API, not the full LLVM C++ API. JIT support is intentionally limited to what is exposed cleanly through LLVM-C ORC LLJIT.
- Docs are currently the README, examples, `devdocs/`, and the generated `.pyi` stub; there is no hosted API reference yet.
- Prebuilt wheels currently target CPython 3.12+ stable ABI on Linux x86_64/aarch64, macOS arm64/x86_64, and Windows x86_64. Other platforms require a source build.
## Development
### Setup
Source/development builds need LLVM 21.1.6 available to CMake. The LLVM archives used for packaging are published at [LLVMParty/llvm-builds v21.1.6](https://github.com/LLVMParty/llvm-builds/releases/tag/v21.1.6).
From a checkout, download the matching LLVM archive and then run `uv sync`:
# Choose the archive matching your platform:
# llvm-21.1.6-linux-x86_64.zip
# llvm-21.1.6-linux-aarch64.zip
# llvm-21.1.6-macos-arm64.zip
# llvm-21.1.6-macos-x86_64.zip
# llvm-21.1.6-windows-x86_64.zip
python tools/ci/install_llvm.py \
--version 21.1.6 \
--archive llvm-21.1.6-linux-x86_64.zip \
--dest .llvm \
--prefix-file .llvm-prefix
uv sync --verbose
If you are using your own LLVM install instead, set `LLVM_ROOT` to its install prefix:
export LLVM_ROOT=/path/to/llvm
uv sync --verbose
If `.llvm-prefix` already points at another LLVM install, delete it first; CMake may reuse it from a previous configure.
For offline builds:
uv sync --offline --no-build-isolation --verbose
For Windows C++/LSP setup, see the Windows development section below.
### Testing
# Main golden-master suite:
# - runs C++ test executables from build/
# - runs paired Python scripts
# - compares Python output against stored C++ behavior
uv run run_tests.py
# Python-only regression scripts in tests/regressions/
uv run run_tests.py --regressions
# Vendored llvm-c-test lit suite against the C test binary
# Rebuilds the vendored C binary before running lit.
uv run run_llvm_c_tests.py
uv run run_llvm_c_tests.py -v
# Vendored llvm-c-test lit suite against the Python implementation
uv run run_llvm_c_tests.py --use-python
# Run the Python llvm-c-test port directly during development
uv run python -m llvm_c_test --targets-list
# Type checking (not a test suite, but commonly run in CI/dev)
uvx ty check
If you want the closest thing to “run everything in this repo”, use:
uv run run_tests.py
uv run run_tests.py --regressions
uv run run_llvm_c_tests.py
uv run run_llvm_c_tests.py --use-python
Python tests here are intended to be executable as standalone scripts
(e.g. `uv run tests/test_module.py` or `uv run tests/regressions/test_const_bytes.py`).
They are generally pytest-compatible too, but direct script execution is the
historical/default style used by `run_tests.py` and for one-off debugging.
`pytest` is still useful for targeting specific regression files or subsets, but
it is not our complete top-level test entrypoint by itself.
### Coverage
# Run with coverage
uv run coverage run run_llvm_c_tests.py --use-python
uv run coverage combine
uv run coverage report --include="llvm_c_test/*"
### Windows source builds and C++ IntelliSense
The wheel is the easiest path on Windows. If you need a source/development build, install Visual Studio or Visual Studio Build Tools with the C++ workload, then fetch LLVM and run `uv sync`:
py -3.12 tools\ci\install_llvm.py `
--version 21.1.6 `
--archive llvm-21.1.6-windows-x86_64.zip `
--dest .llvm `
--prefix-file .llvm-prefix
uv sync --verbose
If you are using your own LLVM install instead:
$env:LLVM_ROOT = "C:\path\to\llvm"
uv sync --verbose
For C++ local development with an LSP, create a local `CMakeUserPresets.json` at the repository root. CMake gets LLVM from `.llvm-prefix` created above, or from `LLVM_ROOT` on first configure. The compiler paths below assume the usual LLVM installer location; adjust them if your `clang-cl.exe` is elsewhere:
{
"version": 3,
"configurePresets": [
{
"name": "clang-cl",
"displayName": "Ninja with clang-cl",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_C_COMPILER": "C:/Program Files/LLVM/bin/clang-cl.exe",
"CMAKE_CXX_COMPILER": "C:/Program Files/LLVM/bin/clang-cl.exe",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
}
],
"buildPresets": [
{
"name": "clang-cl",
"configurePreset": "clang-cl"
}
]
}
Then configure/build with:
cmake --preset clang-cl
cmake --build --preset clang-cl
If you move or replace the LLVM install, delete or regenerate `.llvm-prefix` from any previous configure.
## License
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
LLVM is licensed under the Apache License v2.0 with LLVM Exceptions.