---
title: Lisp-Z3 Interface
url: https://www.emergentmind.com/topics/lisp-z3
type: topic
---

# Lisp-Z3 Interface

Searching arXiv for recent papers directly relevant to Lisp-Z3 and closely related ACL2/Z3 integrations.
Lisp-Z3 is a Common Lisp interface to the Z3 satisfiability modulo theories solver, introduced as an extension to the ACL2s Systems Programming Framework (ASPF) that allows tools written using the full feature set of Common Lisp to use both ACL2/s and Z3 as services. Although designed to integrate smoothly with ACL2 and ACL2s, it does not depend on the availability of ACL2/s and is usable by anyone who would like to interact with Z3 from Common Lisp [2507.19014].

## 1. Definition and position within the ACL2s ecosystem

Lisp-Z3 is described as a major extension of ASPF. In the terminology used around ACL2, ACL2 is a first-order, untyped theorem prover based on a subset of Common Lisp, ACL2s is an extension of ACL2 with facilities including Defdata types, `definec`/`property`, counterexample generation, and strong termination analysis, and ASPF lets one write full Common Lisp code that uses ACL2 or ACL2s as a backend prover [2507.19014].

Within that setting, Lisp-Z3 adds a second backend reasoner. A single Common Lisp tool can call ACL2 or ACL2s for interactive theorem proving, type checking, termination reasoning, and related services, while also calling Z3 for SMT solving. The same interface is also usable in plain Common Lisp, without ACL2 or ACL2s. This division of labor is central to the design: ACL2/ACL2s and Z3 are not merged into a single logic, but exposed as complementary services through one systems-programming interface [2507.19014].

The motivating requirements were incremental solving, low latency, integration with ASPF, and portability across Lisp implementations. The paper states that existing interfaces either did not integrate well with ACL2s/ASPF, did not provide the required latency or incremental capabilities, or were not sufficiently portable across Lisp implementations. Lisp-Z3 was therefore designed as both an ACL2s-oriented extension and a general-purpose Common Lisp interface to Z3 [2507.19014].

## 2. Architecture, bindings, and internal representation

Lisp-Z3 has a two-layer architecture. The low-level layer consists of direct wrappers for Z3’s C API via CFFI. The high-level layer manages Z3 contexts and solvers, translates Lisp S-expressions into Z3 abstract syntax trees, mimics SMT-LIB2 commands such as `declare-const`, `assert`, and `check-sat`, and converts models back into Lisp data [2507.19014].

The binding infrastructure uses CFFI and the CFFI Groveller. A Grovel file describes Z3 types of interest, the Groveller generates and runs C code, and the result is turned into Lisp information about type sizes and layouts. A Python script reads Z3’s headers and generates the Grovel file so that Lisp-Z3 can stay synchronized with relatively recent Z3 versions. This architecture is explicitly intended to support portability across multiple Common Lisp implementations and platforms [2507.19014].

At the solver level, Lisp-Z3 mirrors Z3’s context-based execution model. On initialization, `solver-init` creates a Z3 context and a default solver. Lisp-side wrapper objects carry both the underlying C pointer and the corresponding Z3 context. Lisp-Z3 also maintains an environment stack aligned with the solver’s assertion stack. Variable declarations are attached to assertion levels, and the environment enforces a Common Lisp-like discipline in which, at any time, a name denotes at most one free variable, with no overloading by sort [2507.19014].

The sort interface follows Z3’s many-sorted logic while remaining Lisp-oriented. Sort specifiers can denote non-parametric sorts such as `:int`, `:bool`, and `:string`, parametric sorts such as `(:seq :int)`, and function ranks of the form `(:fn (<p1> ... <pn>) <r>)`. Lisp-Z3 also exposes user-defined sorts through `register-enum-sort` and `register-tuple-sort`, implemented internally using Z3’s datatype mechanisms [2507.19014].

Memory management is handled by combining Z3’s reference counting with Lisp finalization. The paper states that Lisp-Z3 uses the `trivial-garbage` library so that each wrapper object registers a finalizer which decrements the Z3 reference count when the Lisp object is garbage-collected. This avoids manual reference-count management in user code [2507.19014].

## 3. Programming model and solver interaction

The high-level interface is SMT-LIB2-like. A typical workflow is to call `solver-init`, declare variables with `declare-const`, assert constraints with `z3-assert`, call `check-sat`, and inspect the resulting model through `get-model`, `get-model-as-assignment`, or `eval-under-model`. The paper’s basic example declares `x : Bool` and `y : Int`, asserts `(and x (>= y 5))`, receives `:SAT` from `check-sat`, and obtains the assignment `((X T) (Y 5))` from `get-model-as-assignment` [2507.19014].

`z3-assert` also supports inline declarations. Instead of declaring variables separately, one may write a declaration list such as `(x :bool y :int)` as the first argument to `z3-assert`. Those declarations are inserted into the solver’s environment stack and remain available at the appropriate assertion levels. This gives the interface a direct resemblance to SMT-LIB2 while preserving normal Lisp dataflow and macro expansion patterns [2507.19014].

Incremental solving is exposed through `solver-push` and `solver-pop`, directly reflecting Z3’s assertion stack. This is not merely a convenience feature; it is one of the design points emphasized in the paper because it supports applications that repeatedly refine or backtrack constraints, such as SeqSolve and the Sudoku solver. The environment stack is synchronized with these push/pop operations [2507.19014].

Model interpretation is intentionally Lisp-facing. Sequences represented by Z3 as nested concatenations are converted into Lisp values when possible. Function interpretations are converted into map-like Lisp structures consisting of explicit entries together with a `:default` value. `eval-under-model` allows a Lisp expression parsed in the same way as `z3-assert` to be evaluated in a model, optionally completing the model if some symbols do not yet have interpretations [2507.19014].

The interface also exposes lower-level access close to the C API, including contexts, solvers, AST construction, and model traversal. The paper presents this layer as deliberately verbose and tedious compared with the high-level API; its role is completeness and direct access rather than everyday usability [2507.19014].

## 4. Semantics, soundness boundaries, and relation to ACL2 tooling

Expressions passed through `z3-assert` are interpreted with Z3 semantics, not ACL2 semantics and not ordinary Common Lisp semantics. The paper emphasizes this with a concrete mismatch: an ACL2 property asserting `(= (+ x y) y)` for `x :bool` and `y :int` is true in ACL2 because non-numeric arguments to `+` are treated as `0`, whereas a naïve Z3 translation via Lisp-Z3 can make the negation satisfiable because Z3 treats `true` as `1` and `false` as `0` in arithmetic [2507.19014].

For that reason, Lisp-Z3 is not presented as a proof tactic or clause processor. It is a tool-level integration. ACL2s may be used for definitional reasoning, theorem proving, type discipline, and counterexample infrastructure, while Z3 is used for SMT-native reasoning over arithmetic, arrays, sequences, strings, bit-vectors, and related theories. When soundness inside ACL2 is required, Lisp-Z3 can be used as an oracle whose outputs are subsequently validated in ACL2 rather than trusted as a semantics-preserving ACL2 proof step [2507.19014].

This design differs sharply from Smtlink 2.0, which is a verified bridge from ACL2 to SMT solvers. Smtlink 2.0 translates ACL2 goals using a sequence of verified clause processors and computed hints, and only the final transliteration from ACL2 to Z3’s Python interface requires a trusted clause processor. It also supports `fty::defprod`, `deflist`, `defalist`, and `defoption` by using Z3’s arrays and user-defined data types [1810.04317]. Lisp-Z3, by contrast, prioritizes systems programming, latency, incremental interaction, and direct Common Lisp access to Z3 rather than proof-producing translation from ACL2 logic.

A plausible implication is that Lisp-Z3 and Smtlink occupy complementary positions. One provides a programming-oriented bridge from Common Lisp and ACL2s-centered tools to Z3; the other provides a proof-oriented, soundness-focused translation pipeline from ACL2 formulas to SMT obligations.

## 5. Applications and reported performance

The paper discusses three applications: a Sudoku solver, SeqSolve, and hardware-in-the-loop fuzzing of wireless routers. In all three cases, Lisp-Z3 is used as a service inside a larger Common Lisp system, and in the latter two cases the applications explicitly leverage the ability to integrate Z3 with ACL2s code [2507.19014].

| Application | Role of Lisp-Z3 | Reported outcome |
|---|---|---|
| Sudoku solver | Integer variables, range constraints, `distinct`, incremental `push`/`pop` | Pure Common Lisp + Lisp-Z3; ACL2/ACL2s not required |
| SeqSolve | Incremental LIA solving over string lengths within an ACL2s-based string solver | Solved a larger number of benchmark problems more quickly than any other existing solver at the time of its publishing |
| Wireless-router fuzzing | Z3 solves numeric constraints; ACL2s enumerators generate structured frames | Combined method was nearly two orders of magnitude faster than pure Z3 and pure ACL2s over the satisfiable range |

The Sudoku solver is encoded in the standard SMT style: 81 integer variables constrained to the range \(1\) through \(9\), together with row, column, and \(3 \times 3\) box distinctness constraints. The structural constraints are asserted once, and individual puzzles are solved by pushing a solver frame, asserting clue constraints, checking satisfiability, extracting a model, and popping the frame [2507.19014].

SeqSolve is a string solver that combines an algorithm due to Kumar and Manolios, ACL2s code for structured data and invariants, and Lisp-Z3 for the linear arithmetic constraints over string lengths that arise during solving. The paper states that SeqSolve solved a larger number of benchmark problems more quickly than any other existing solver at the time of its publishing. This application highlights incremental solving and low-overhead repeated Z3 invocation [2507.19014].

The wireless-router application targets IEEE 802.11 probe request frames. The frame body is a sequence of elements whose structure depends on element identifiers, and valid frame sizes lie between 172 and 2741 bytes. A pure Z3 approach and a pure ACL2s enumerative approach were both considered. The successful design, called ACL2s-ETC, splits the task: Z3 solves numeric constraints such as frame size, and ACL2s defdata enumerators generate structured packet contents consistent with those constraints. The reported result is nearly two orders of magnitude more throughput than pure Z3 or pure ACL2s over the satisfiable range, while low latency remained a central goal [2507.19014].

## 6. Ecosystem, limitations, and future directions

Because Lisp-Z3 is a Common Lisp interface to the Z3 C API rather than a separate solver, it inherits changes in the underlying Z3 ecosystem. This matters for backend substitution. Z3-Noodler, for example, is described as a fork of Z3 that preserves the interface, and for users of language bindings, including Lisp, it should be a drop-in replacement binary or library while preserving Lisp-side code unchanged [2310.08327]. Likewise, PolySAT is integrated into Z3, and from the perspective of users driving Z3 through language bindings, it changes how many bit-vector problems are solved by keeping arithmetic-heavy reasoning at the word level rather than immediately bit-blasting [2406.04696].

The paper also records several limitations of Lisp-Z3 itself. Some Z3 model values are only approximately translated back to Lisp: algebraic numbers are approximated as floating-point numbers by default, losing exactness. Not all Z3 features and SMT-LIB2 constructs are supported. Only single-rank function declarations are currently allowed, and some advanced datatypes and theories may require extending Lisp-Z3. Keeping the low-level bindings aligned with different Z3 versions also requires ongoing effort, even though Grovel-file generation is partially automated [2507.19014].

The stated future direction is integration into ACL2s for more powerful automated support for dependent types and, in particular, more efficient generation of counterexamples to properties involving dependent types. The paper connects this to the broader idea of Enumerative Data Types Modulo Theories, in which type parameters are constrained by SMT-decidable theories: Z3 is used to solve for parameter values, and ACL2s enumerators are then used to generate concrete data consistent with those parameters [2507.19014].

This suggests a long-term role for Lisp-Z3 beyond simple FFI access. It functions as infrastructure for hybrid reasoning systems in which Common Lisp orchestrates interaction, ACL2s contributes definitional and proof-oriented machinery, and Z3 provides theory solving with incremental and low-latency access. Within that architecture, Lisp-Z3 is less a proof bridge than a programmable mediation layer between interactive theorem proving and SMT solving [2507.19014].

Source: https://www.emergentmind.com/topics/lisp-z3