LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
Tool Guides

llzk-opt

llzk-opt is a version of the mlir-opt tool that supports passes on LLZK IR files. You can refer to the mlir-opt documentation for a general overview of the operation of *-opt tooling, but note that many options and passes available in mlir-opt are not available in llzk-opt. llzk-opt -h will show a list of all available flags and options.

LLZK-Specific Options

-I <directory> : Directory of include files

LLZK Pass Documentation

Analysis Passes

-llzk-print-call-graph

Print the LLZK module's call graph.

Options
-stream : Specifies the stream to which the pass prints.

-llzk-print-call-graph-sccs

Print the SCCs from the LLZK module's call graph.

Options
-stream : Specifies the stream to which the pass prints.

-llzk-print-constraint-dependency-graphs

Print constraint dependency graph for all LLZK structs.

Options
-stream : Specifies the stream to which the pass prints.
-intraprocedural : Whether to run the analysis intra-procedurally only (default is false).

-llzk-print-interval-analysis

Print interval analysis results for all LLZK structs.

Options
-stream : Specifies the stream to which the pass prints.
-field : The field to use for interval analysis. If supplied, this always overrides the module's detected field. If omitted, the pass first tries to detect a single field from the enclosing module's felt usage and otherwise falls back to bn128. Supported fields: bn128/bn254, babybear, goldilocks, grumpkin, koalabear, mersenne31
-propagate-input-constraints : Whether to propagate constraints on inputs from @constrain to @compute functions. This allows for tighter intervals to possibly be found for computed values, assuming that the witness generator would include constraints as assertions during the computation.
-print-solver-constraints : Whether to output SMT solver constraints along with intervals.
-print-compute-intervals : Whether to print compute function intervals (default only prints constrain function intervals).
-print-unreduced-intervals : Whether to print tracked unreduced intervals alongside reduced interval summaries.
-print-ssa-intervals : Whether to print per-SSA intervals for function arguments and scalar op results.

-llzk-print-predecessors

Print the predecessors of all operations.

Options
-stream : Specifies the stream to which the pass prints.
-prerun : Whether to pre-run the required dataflow analyses (e.g., liveness analysis).

-llzk-print-symbol-def-tree

Print symbol definition tree.

Options
-stream : Specifies the stream to which the pass prints.
-saveDot : Whether to dump the graph to DOT format.

-llzk-print-symbol-use-graph

Print symbol use graph.

Options
-stream : Specifies the stream to which the pass prints.
-saveDot : Whether to dump the graph to DOT format.

General Transformation Passes

-llzk-compute-constrain-to-product

_Replace separate @compute and @constrain functions in a struct with a single @product function_

Replace separate @compute and @constrain functions in a struct with a single @product function

Options
-root-struct : Root struct at which to start alignment (default to `@Main`)

-llzk-duplicate-op-elim

Remove redundant operations

Remove equivalent memory-effect-free operations and duplicate LLZK constraints.

Pass should be run after llzk-duplicate-read-write-elim for maximum effect.

-llzk-duplicate-read-write-elim

Remove redundant reads and writes

Remove redundant or unnecessary reads and writes to struct members, arrays, globals, and RAM. Global writes are removed when they are overwritten or write an already-known value. RAM stores require the same translated address SSA value.

Struct-member accesses match only when the base, member, table offset, affine map metadata, and translated affine operands match. Omitted member-read offsets and member writes denote the current row.

Stateful reads are reused when the current analysis state proves the read observes the same value. Writes and stores update that known state, while unknown or non-read state effects invalidate it.

-llzk-enforce-no-overwrite

Checks that every struct member is written exactly once

This pass currently reports an error if any struct member may not be written exactly once (i.e. overwritten or left uninitialized), and does not attempt to perform any repairs (e.g. SSA-ifying overwritten struct members, or default-initializing unwritten members). This pass overapproximates conditionals, and can result in false positives.

-llzk-fuse-product-loops

_Fuse matching witness/constraint loops in a @product function_

Fuse matching witness/constraint loops in a @product function

-llzk-inline-free-functions

Inline calls to free functions

Inlines function.call operations inside function.def bodies when the target is a free function.def directly inside the pass's root builtin.module. Calls to functions in nested or included modules, and calls outside function.def bodies, are left unchanged. After inlining, root free functions that have no remaining symbol uses anywhere in the module are erased. This cleanup applies to every non-external root free function, regardless of its symbol visibility or whether any call to it was inlined.

Inlining is best effort: free functions that participate in a call cycle are skipped (inlining one would re-materialize its calls forever). If inlining a callee fails, its remaining calls are left in place with a warning while processing continues with other callees.

-llzk-poly-lowering-pass

Lower the degree of all polynomial equations to a specified maximum

Rewrites constraint expressions into an (observationally) equivalent system where the degree of every polynomial is less than or equal to the specified maximum.

High-degree subexpressions are factored into auxiliary struct members. The pass also recurses through degree-neutral roots such as felt.add, felt.sub, and felt.neg, so composite equality operands and struct constrain call arguments satisfy their degree bounds.

The pass expects already-flattened, straight-line compute and constrain bodies. Run llzk-flatten or another control-flow lowering pass before this pass; nested-region, successor-bearing, or multi-block functions are rejected instead of being rewritten with component-lifetime auxiliary members.

This pass is best used on already-flattened input as part of the -llzk-full-poly-lowering pipeline, which includes additional cleanup passes to ensure correctness and optimal performance.

Options
-max-degree : Maximum degree of constraint polynomials (default 2, minimum 2)

-llzk-remove-unused-discardable-allocations

Remove unread discardable allocations and their dead stores

Remove ops with the selected allocator operation name when they are marked with MemAlloc<DiscardableAllocationResource>, the allocation has no reads, and every direct user is a discardable allocation accessor that can be erased as a dead store.

Options
-allocator-op : Operation name of the discardable allocator to remove

-llzk-unused-declaration-elim

Remove unused member and struct declarations

Remove member and struct declarations that are unused within the current compilation unit. Note that this pass may cause linking issues with external modules that depend on any unused member and struct declarations from this compilation unit.

Pass should be run after llzk-duplicate-read-write-elim and llzk-duplicate-op-elim for maximum effect.

Options
-remove-structs : Whether to remove unused struct definitions as well. Requires module to declare a Main component, otherwise all components will appear unused.

-llzk-while-to-for

Converts scf.while loops to equivalent scf.for loops when possible

This pass identifies scf.while loops that have an induction variable and a uniform step, and converts them to equivalent scf.for loops, which are preferred by some analyses. This pass may introduce some spurious felt/index casts which should be cleaned up by –canonicalize.

'array' Dialect Transformation Passes

-llzk-array-to-scalar

Replace arrays with scalar values

Replace known-shape arrays with the proper number of scalar values

'polymorphic' Dialect Transformation Passes

-llzk-drop-empty-templates

Remove empty templates

Performs the following transformations:

  • Convert templates with no constant parameters or expressions into modules.
  • Remove templates with no struct or function definitions.

-llzk-flatten

Flatten structs and unroll loops

Performs the following transformations:

  • Instantiate affine_map parameters of StructType and ArrayType to constant values using the arguments at the instantiation site
  • Replace parameterized structs with flattened (i.e., no parameter) versions of those structs based on requested return type at calls to compute() functions and unroll loops
  • Unroll loops
Options
-max-iter : Maximum number of times the pass will run if a fixpoint is not reached earlier. Unrolling loops can provide more opportunities for instantiating structs but the converse is true as well. Thus, the pass will run multiple times until no further changes can be made or the upper limit provided in this option is reached.
-cleanup : Specifies the extent to which unused parameterized definitions (i.e. structs or free functions within a `poly.template`) are removed during the flattening pass.

-llzk-infer-tvar

Infer concrete function types for polymorphic type variables

Infers concrete replacements for !poly.tvar template parameters from operations in function.def or poly.expr bodies, such as poly.unifiable_cast between a type variable and a more concrete type (which means for maximum effect you should run it before other optimization passes that can remove poly.unifiable_cast). Rewrites affected function signatures and body types, removes redundant casts, drops resolved poly.param declarations, and updates call-site template parameter lists.

The pass also creates template-local instantiated versions of functions for each type-variable replacement. The llzk-flatten pass performs similar instantiation of type variables but only as part of its more extensive lowering and flattening, all of which may not be desired in some cases (hence the utility of this pass).

This pass may result in poly.template containing no poly.param or poly.expr. Run llzk-drop-empty-templates after this pass to simplify these templates.

-llzk-specialize-wildcard-arrays

Refine wildcard array casts and specialize concrete call targets

Refines poly.unifiable_cast results when wildcard array.type dimensions can be replaced with concrete integer sizes from the input type. Then specializes free functions, extern declarations, and whole structs for calls whose wildcard array dimensions have become concrete.

This pass is intended to run after llzk-flatten. It iterates to a fixpoint so newly-refined cast result types can enable further callable specialization in later iterations.

Options
-max-iter : Maximum number of iterations before the pass gives up reaching a fixpoint.

Validation Passes

-llzk-validate-member-writes

Detect multiple and missing writes to the same member of a component.

Detect multiple and missing writes to the same member of a component.

Note that this is overapproximate (i.e., some writes may erroneously be flagged as overwrites, and some members may erroneously be marked unwritten).

llzk-translate

llzk-translate is a version of the mlir-translate tool. Includes translations for backends specific to LLZK along with translations available in upstream MLIR (i.e. LLVM or C++). llzk-translate -h will show a list of all available flags and options.

The tool expects that the IR has already been converted to the backend's dialect IR. For example:

llzk-opt <input.llzk> --llzk-to-pcl | llzk-translate --pcl-to-lisp

LLZK-Specific Options

--pcl-to-lisp Translates from PCL IR to PCL lisp
--smt-to-smtlib Translates from SMT to SMTLIB
--zklean-to-lean Translates from zkLean dialects IR to Lean code

llzk-witgen

llzk-witgen executes LLZK witness-generation logic for the concrete main component declared by llzk.main. It evaluates compute() and prints JSON for either the public outputs of the main component or the full generated witness signal set.

Basic Usage

llzk-witgen <input.llzk> --inputs <input.json>

LLZK-Specific Options

--inputs <file> JSON file containing main compute inputs
-I <directory> Directory of include files
--backend=<name> Execution backend: interpreter or execution-engine
--output-scope=<name> Output scope: public or full-witness
--dump-jit-core Print the pre-LLVM JIT module
--dump-jit-llvm Print the post-LLVM JIT module
--uninitialized-behavior Control default handling of uninitialized witness values
--uninitialized-seed Seed used for randomized uninitialized witness values

Input Format

The --inputs file must contain a top-level JSON object or JSON array.

  • A JSON object is keyed by function.arg_name attributes on the main compute() function arguments.
  • A JSON array is interpreted positionally in declared argument order.

At the main boundary, llzk-witgen only supports felt and array<... x felt> inputs, due to the restrictions posed on llzk.main components. Field element values are accepted in the same JSON form used by the witgen tests, namely JSON integers or decimal strings.

Output Format

llzk-witgen writes one JSON object to stdout. The exact shape depends on --output-scope.

  • --output-scope=public is the default.
    • The output JSON contains only the public outputs of the main component.
    • Public struct members become JSON object fields.
    • Public felt arrays become JSON arrays.
    • Field element leaves are rendered as decimal strings.
  • --output-scope=full-witness
    • The output JSON contains two top-level objects: inputs and signals.
    • inputs records the main compute() arguments using their function.arg_name attributes when available, or stable fallback names such as arg0, arg1, and so on for positional inputs.
    • signals records all witness signals reachable from the returned main struct, including both public and private signals.
    • Non-signal leaves are omitted, though non-signal struct containers may still appear when needed to reach nested signals.
    • Felt arrays remain JSON arrays and field element leaves remain decimal strings.

Backends

llzk-witgen currently supports two execution backends:

  • --backend=interpreter Executes LLZK @compute logic directly over the preprocessed MLIR.
  • --backend=execution-engine Lowers preprocessed LLZK @compute IR to built-in MLIR dialects that can be natively converted to LLVM IR, then executes it with mlir::ExecutionEngine.

The default backend is interpreter, as the execution-engine does not currently support all LLZK features due to existing lowering limitations (e.g., in the -llzk-flattening pass).

Preprocessing

Before execution, llzk-witgen performs the preprocessing needed to make witness generation concrete and executable:

  • include inlining
  • flattening rooted at llzk.main when required
  • affine lowering for execution-engine mode
  • subcomponent inlining for execution-engine mode

Template parameters and affine instantiations are therefore supported only when they can be fully resolved by the preprocessing pipeline before execution.

llzk-smt-check

llzk-smt-check runs an external SMT solver on an SMT-LIB 2 script and reports the result of each check-sat stage. It is intended to consume the staged SMT-LIB emitted by llzk-opt --smt-to-smtlib, including explicit metadata produced from smt.set_info ops such as (set-info :llzk-stage "pre") and (set-info :status unsat).

Basic Usage

llzk-smt-check input.smt2
llzk-opt --smt-to-smtlib -o /dev/null input.llzk | llzk-smt-check -

--smt-to-smtlib exports the unique top-level smt.solver directly contained in the root builtin.module. Module-scope func.func definitions are treated as helpers that may be called from within that solver body. Exported scripts preserve any smt.set_logic operation in that solver and otherwise begin with the conservative fallback (set-logic ALL). Any staged metadata consumed by llzk-smt-check must be introduced explicitly in the SMT dialect IR using smt.set_info, for example:

smt.set_info ":llzk-root" "CheckGate"
smt.set_info ":llzk-stage" "pre"
smt.set_info ":status" unsat

smt.check lowers only to a bare (check-sat). Because SMT-LIB scripts cannot branch on check-sat results internally, --smt-to-smtlib accepts only smt.check operations whose sat, unknown, and unsat regions are empty. Result-dependent script behavior would need a higher-level driver format outside SMT-LIB.

LLZK-Specific Options

--solver-binary=<path> SMT solver executable to run (default: z3)
--quiet Suppress per-stage summaries and rely on exit status
--dump-raw-output Print raw solver stdout after the stage summaries

Behavior

  • The tool reads SMT-LIB from a file or stdin.
  • set-info :status <sat|unsat|unknown> provides the expected result for the subsequent check-sat.
  • set-info :llzk-stage "<name>" and set-info :llzk-root "<name>" provide LLZK-specific stage and root labels for the subsequent summaries.
  • When expected-result annotations are present, every reported solver result must match the expected sat, unsat, or unknown result for the tool to succeed.
  • Without annotations, the tool still executes the script and labels checks as check[0], check[1], and so on.
  • Any solver launch failure, malformed output, result-count mismatch, or stage mismatch causes a non-zero exit status.

llzk-lsp-server

cmake --build <build dir> --target llzk-lsp-server will produce an LLZK-specific LSP server that can be used in an IDE to provide language information for LLZK. Refer to the MLIR LSP documentation for a more detailed explanation of the MLIR LSP tools and how to set them up in your IDE.

Previous Next
Setup Contribution Guide