How to Enable Z3 Solver Support in LLVM for Symbolic Execution
Enable Z3 solver support in LLVM by configuring your build with -DLLVM_ENABLE_Z3_SOLVER=ON and optionally specifying -DLLVM_Z3_INSTALL_DIR to locate the Z3 headers and libraries, which defines the LLVM_WITH_Z3 macro and exposes the CreateZ3Solver() factory for runtime use.
LLVM's symbolic execution engine leverages SMT solvers to analyze program paths and verify constraints. The llvm/llvm-project repository includes a native Z3 theorem prover integration implemented in llvm/lib/Support/Z3Solver.cpp, but this functionality is disabled by default and requires explicit build-time configuration to enable Z3 solver support in LLVM.
Prerequisites for Z3 Integration
Before configuring LLVM, you must install the Z3 development libraries on your system. The build requires the Z3 header file z3.h and the library file (libz3.so on Linux or libz3.a for static linking) to be available in standard or user-specified paths.
If Z3 is installed in a non-standard location, you will need the root path to pass to CMake during the LLVM configuration phase.
CMake Configuration Flags
Two CMake options control how LLVM discovers and enables Z3 support:
LLVM_ENABLE_Z3_SOLVER – Set this boolean option to ON to enable the Z3 backend. According to the source code in llvm/lib/Support/Z3Solver.cpp, this defines the LLVM_WITH_Z3 preprocessor macro, which gates the compilation of all Z3-specific solver code behind #if LLVM_WITH_Z3 blocks.
LLVM_Z3_INSTALL_DIR – Specify the root directory of your Z3 installation when it resides outside standard system paths. The module llvm/cmake/modules/FindZ3.cmake checks this variable first, then searches system paths for z3.h and the Z3 libraries.
Example Configuration Command
Configure LLVM with Z3 support using the following CMake invocation:
cmake -G Ninja \
-DLLVM_ENABLE_PROJECTS="llvm;clang" \
-DLLVM_ENABLE_Z3_SOLVER=ON \
-DLLVM_Z3_INSTALL_DIR=/opt/z3 \
/path/to/llvm-project/llvm
This command enables the solver, points to a custom Z3 installation at /opt/z3, and prepares the build for the Ninja generator.
Building LLVM with Z3 Support
After running CMake, execute your build command (ninja or make). The Z3 solver implementation compiles only when LLVM_WITH_Z3 is defined. If the Z3 headers or libraries are missing or incorrectly specified, the build will fail with linker errors or "file not found" messages for z3.h.
A successful build links against the Z3 libraries and includes the factory function for creating solver instances.
Runtime Usage of the Z3 Solver
Once LLVM is compiled with Z3 support, obtain a Z3-backed solver instance through the factory function defined at the end of llvm/lib/Support/Z3Solver.cpp:
llvm::SMTSolverRef Solver = llvm::CreateZ3Solver();
If LLVM was built without Z3 support, calling this function triggers a fatal error that terminates the program with a message instructing you to rebuild with -DLLVM_ENABLE_Z3_SOLVER=ON.
Working with Bitvector Constraints
The following example demonstrates using LLVM's SMT API to create bitvector variables and check satisfiability:
#include "llvm/Support/SMTAPI.h"
int main() {
// Obtain a Z3 solver (requires LLVM built with Z3 support)
llvm::SMTSolverRef Solver = llvm::CreateZ3Solver();
// Create 8-bit bitvector sort
auto BV8 = Solver->getBitvectorSort(8);
// Create symbolic variables
auto X = Solver->mkSymbol("x", BV8);
auto Y = Solver->mkSymbol("y", BV8);
// Build constraint: x + y == 42
auto Sum = Solver->mkBVAdd(X, Y);
auto C42 = Solver->mkBitvector(llvm::APSInt(42), 8);
Solver->addConstraint(Solver->mkEqual(Sum, C42));
// Check satisfiability
auto Result = Solver->check();
if (Result && *Result)
llvm::outs() << "SAT\n";
else
llvm::outs() << "UNSAT or UNKNOWN\n";
return 0;
}
The solver supports bitvector arithmetic, boolean logic, and floating-point operations as implemented in the Z3 backend.
Summary
- Enable Z3 solver support in LLVM by setting
-DLLVM_ENABLE_Z3_SOLVER=ONduring CMake configuration to define theLLVM_WITH_Z3macro. - Use
LLVM_Z3_INSTALL_DIRto specify custom Z3 installation paths when not using system defaults. - The integration lives in
llvm/lib/Support/Z3Solver.cpp, guarded by conditional compilation directives. - Call
llvm::CreateZ3Solver()at runtime to obtain a Z3-backed solver instance. - The API supports bitvectors, floating-point constraints, and boolean operations for symbolic execution tasks.
Frequently Asked Questions
What happens if I call CreateZ3Solver() without enabling Z3 during the build?
The function implementation includes a fallback branch that terminates the program with a fatal error, displaying a message that reminds you to rebuild LLVM with -DLLVM_ENABLE_Z3_SOLVER=ON. This prevents symbolic execution tools from silently failing when solver support is expected but not compiled.
Does LLVM support alternative SMT solvers besides Z3?
While LLVM's llvm/lib/Support/SMTAPI.h defines a generic solver interface, the upstream llvm/llvm-project repository currently maintains only the Z3 backend in llvm/lib/Support/Z3Solver.cpp. Supporting additional solvers would require implementing the SMT API for those specific backends.
Can I use a system-installed Z3 without specifying LLVM_Z3_INSTALL_DIR?
Yes. The FindZ3.cmake module searches standard system paths (such as /usr/lib and /usr/include) automatically. You only need to set LLVM_Z3_INSTALL_DIR when Z3 is installed in a custom prefix or when you need to override the system version with a specific build.
Are floating-point operations supported through the Z3 backend?
Yes. The implementation in llvm/lib/Support/Z3Solver.cpp supports floating-point sorts and arithmetic operations alongside bitvectors and booleans, enabling comprehensive symbolic execution analysis of numeric computations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →