LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
TransformationPasses.td
Go to the documentation of this file.
1//===-- TransformationPasses.td ------------------------*- tablegen -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
10#ifndef LLZK_POLYMORPHIC_TRANSFORMATION_PASSES_TD
11#define LLZK_POLYMORPHIC_TRANSFORMATION_PASSES_TD
12
13include "llzk/Pass/PassBase.td"
14include "mlir/IR/EnumAttr.td"
15
16def FlatteningCleanupModeDescription {
17 string r =
18 "Specifies the extent to which unused parameterized definitions (i.e. "
19 "structs or free functions within a `poly.template`) are removed during "
20 "the flattening pass.";
21}
22
23def FlatteningCleanupMode
24 : I32EnumAttr<"FlatteningCleanupMode", FlatteningCleanupModeDescription.r,
25 [
26 // Unspecified: Leave the cleanup choice to the caller
27 // (defaults to `preimage` if not specified).
28 I32EnumAttrCase<"Unspecified", 0, "unspecified">,
29 // Disabled: No definitions are deleted.
30 I32EnumAttrCase<"Disabled", 1, "disabled">,
31 // Preimage: Only definitions that were replaced with
32 // concrete instantiations are deleted.
33 I32EnumAttrCase<"Preimage", 2, "preimage">,
34 // ConcreteAsRoot: All definitions that cannot be reached
35 // by a use-def chain from some concrete definition are
36 // deleted.
37 I32EnumAttrCase<"ConcreteAsRoot", 3, "concrete-as-root">,
38 // MainAsRoot: All definitions that cannot be reached by a
39 // use-def chain from the main struct are deleted.
40 I32EnumAttrCase<"MainAsRoot", 4, "main-as-root">,
41]> {
42 let cppNamespace = "::llzk::polymorphic";
43 let genSpecializedAttr = 0;
44}
45
46def EmptyTemplateRemovalPass : LLZKPass<"llzk-drop-empty-templates"> {
47 let summary = "Remove empty templates";
48 let description = [{
49 Performs the following transformations:
50 - Convert templates with no constant parameters or expressions into modules.
51 - Remove templates with no struct or function definitions.
52 }];
53}
54
55def TemplateMonomorphizationPass : LLZKPass<"llzk-monomorphize-templates"> {
56 let summary =
57 "Specialize structs and free functions reachable from llzk.main";
58 let description = [{
59 Starting with a concrete `llzk.main`, discover and instantiate struct and
60 free-function dependencies until every requested specialization has been
61 processed. Reuse definitions by source symbol and concrete argument list,
62 including reachable definitions with no template parameters. Free-function
63 arguments may be explicit on a call or inferred from its type signature.
64 Preserve source templates and control flow, and update direct type uses and
65 calls to their concrete definitions. A free-function call retained in an
66 unselected branch receives a concrete callee only when struct type rewriting
67 changes its signature.
68
69 Loops remain in the IR rather than being unrolled; discovery evaluates their
70 iterations to identify specializations without expanding the loop bodies.
71 "Rolled" calls and struct types below retain affine parameters that depend on
72 those iterations or on array element indices.
73
74 Affine calls and array values retain their parameterized types. Their
75 `poly.family_specializations` and `poly.family` attributes identify the
76 concrete definitions needed by later elaboration; definitions carry
77 `poly.specialization_id`, `poly.origin` and `poly.arguments` attributes.
78 Free-function clones carry `poly.origin` and `poly.arguments`; their
79 identities are separate from the struct IDs used by families.
80 `poly.family` appears on members and operations producing an array value,
81 including `struct.readm` and `array.new`. Each entry names the rolled struct
82 type, the enclosing array's index tuple, and the matching specialization.
83 The `type` entry retains the original affine-parameterized family type; it
84 identifies which struct field is being specialized within a POD element.
85 `specialization` identifies the concrete clone through its
86 `poly.specialization_id`. For example, an element at index 2 of a Child<i>
87 family retains Child<i> in `type` and names the clone whose `poly.arguments`
88 are [2]. Concrete sibling fields have no family entry.
89 On a rolled call, `poly.family_specializations` lists candidates collected
90 from all matching arrays and affine calls in the source instance. The list
91 may be empty only when a known array has an empty dimension. A consumer
92 selects a target by evaluating the argument type's affine map using the
93 value's affine inputs, often an array element's indices, and matching the
94 resulting arguments to `poly.arguments`.
95 `poly.origin` and every struct name nested in `poly.arguments` are relative
96 to the outermost `llzk.lang` root, which is also the lookup anchor when
97 reloading this metadata. Cache keys use the same argument spelling, so
98 requests from different roots reuse the same specialization. Substitution
99 re-spells type arguments relative to the clone's root; an argument whose
100 definition is outside that root is diagnosed at the instantiation site.
101 Arguments to typed `poly.param` declarations are converted to the declared
102 type using the conversions below, before forming a cache key. An
103 incompatible argument is diagnosed. Felt-to-index conversion requires a
104 known field and a reduced representative in the nonnegative signed 64-bit
105 index range. Fieldless felt literals cannot determine an index. Arguments to felt
106 parameters with a known field are reduced modulo its prime before forming
107 the cache key and substituting uses. A felt literal with no specified field
108 acquires the parameter's declared field; a different explicit field is
109 rejected. Equivalent field values therefore share a specialization. With no
110 known field, index-to-felt conversion requires a nonnegative value and
111 preserves it. Untyped parameters retain their input
112 attributes, so differently spelled numeric values can create distinct
113 specializations. An `i1` parameter accepts only boolean arguments or index
114 zero and one, which become false and true. Other index values fail type
115 conversion at the instantiation site. The normalized boolean binding
116 determines specialization identity and is used for every read and forwarded
117 argument.
118 Forwarding that value to an index parameter produces zero or one.
119 A felt binding must be read with its exact felt type, including its field,
120 even for untyped parameters. Mismatched felt types are diagnosed at
121 `poly.read_const`.
122 Reading it as index or i1 is diagnosed at `poly.read_const`; use an explicit
123 cast after a felt read for numeric conversion. Integer bindings read as i1
124 must be zero or one. Integer-to-felt reads use the same modular conversion
125 as typed arguments, and negative values require a known field modulus.
126 Reads whose type is a type variable use its concrete binding.
127
128 Discovered methods and free functions must be defined and single-block.
129 Supported regions are `scf.if`, signed `scf.for` with known bounds and
130 positive steps, and `scf.while` with a known condition at every check.
131 Unstructured branches are diagnosed. Constant evaluation uses template
132 bindings and fold hooks; global and aggregate reads and call results remain
133 unknown. Function arguments also begin unknown, even when a caller passes
134 a constant. Required specialization arguments must resolve.
135 External free functions retain their declared signatures. Calls with struct
136 types rewritten to distinct clone types are diagnosed, including plain
137 structs, because the declaration would otherwise have a mismatched type.
138 Includes must be inlined before running this pass.
139 }];
140 let options = [Option<
141 "specializationLimit", "max-specializations", "unsigned", "1000",
142 "Maximum number of concrete struct and free-function definitions">];
143}
144
145def ConstraintFlatteningPass : LLZKPass<"llzk-flatten-constraints"> {
146 let summary = "Expand main constrain into straight-line constraints";
147 let description = [{
148 After template monomorphization and child-member visibility preparation,
149 expand main's constrain function and reachable single-block callees.
150 Resolve static branches, signed `for` loops with positive steps, and `while`
151 loops directly, including field-valued iterators and comparisons.
152
153 Local arrays and PODs exist only as concrete paths bound to scalar SSA
154 values. Reads resolve bindings; writes and aggregate assignments update
155 local bindings without emitting allocations, copies, or writes. Aggregate
156 arguments preserve identity and remain read-only during each call. Returned
157 aggregates alias their original roots: caller-local roots remain shared and
158 writable after the call, while function inputs and original storage remain
159 read-only. Component handles preserve identity. Every array access index
160 must resolve at compile time and be in bounds. Reads of uninitialized local
161 leaves and writes to function inputs or original storage are diagnosed.
162
163 Replace only main's constrain body, retaining its signature, original
164 component storage, compute functions, and other definitions. Signal leaves
165 are read through paths rooted in the original arguments. Live non-signal
166 scalar storage is rejected; no witness variables or definitions are inferred
167 from equations or compute functions. Ordinary field arithmetic remains SSA.
168
169 Diagnose symbolic control flow, recursion, unsupported effects, and
170 non-native constraint arithmetic that cannot be constant evaluated (including
171 division and inversion). Non-native field operations are evaluated using the
172 current call and iteration bindings and replaced with constants when foldable.
173 Static integer arithmetic, affine maps, casts, and comparisons are evaluated only
174 as structural facts using their fold hooks and declared overflow semantics;
175 pure symbolic computations must be dead after expansion. Both felt-to-index
176 and integer-to-felt casts in assert mode must fold to statically valid
177 conversions even when unused. Unresolved or failing range checks for either
178 cast are diagnosed, never discarded.
179 Every surviving operation is checked independently: only felt constants,
180 felt add/subtract/multiply/negate expressions, felt equalities, original
181 storage reads, index/i1 constants, and conversions of constant integers to
182 felt are permitted. Validated integer-to-felt assertions are retained even
183 when their results are unused. Any failed expansion or validation fails the
184 entire pass.
185
186 Equality constraints currently require scalar felt operands; array equality
187 and constrain.in are unsupported. Symbolic if/select conditions are rejected.
188 Callees must be concrete, defined, and single-block. Recursion is unsupported
189 and call nesting is limited to 256. Signed integer and index induction
190 variables are supported. Original family handles retain their rolled types.
191 Member reads use the concrete specialization's field types, which must unify
192 with the exposed rolled declarations. Specialization selection and concrete
193 path validation stay inside the pass; no new handle operations are emitted.
194 }];
195}
196
197def FlatteningPass : LLZKPass<"llzk-flatten"> {
198 let summary = "Flatten structs and unroll loops";
199 let description = [{
200 Performs the following transformations:
201 - Instantiate `affine_map` parameters of StructType and ArrayType
202 to constant values using the arguments at the instantiation site
203 - Replace parameterized structs with flattened (i.e., no parameter)
204 versions of those structs based on requested return type at calls
205 to `compute()` functions and unroll loops
206 - Unroll loops
207 }];
208 // Implementation note: These options should be kept in sync with
209 // `StructInliningFlatteningOptions` in `LLZKTransformationPassPipelines.h`.
210 let options =
211 [Option<"iterationLimit", "max-iter", "unsigned",
212 /* default */ "1000",
213 "Maximum number of times the pass will run if a fixpoint "
214 "is not reached earlier. Unrolling loops can provide more "
215 "opportunities for instantiating structs but the converse "
216 "is true as well. Thus, the pass will run multiple times "
217 "until no further changes can be made or the upper limit "
218 "provided in this option is reached.">,
219 Option<"cleanupMode", "cleanup",
220 "::llzk::polymorphic::FlatteningCleanupMode",
221 /* default */
222 "::llzk::polymorphic::FlatteningCleanupMode::Unspecified",
223 FlatteningCleanupModeDescription.r, [{::llvm::cl::values(
224 clEnumValN(::llzk::polymorphic::FlatteningCleanupMode::Unspecified,
225 stringifyFlatteningCleanupMode(::llzk::polymorphic::FlatteningCleanupMode::Unspecified),
226 "Use the cleanup mode specified by the calling pipeline (defaults to `preimage` if not specified)."),
227 clEnumValN(::llzk::polymorphic::FlatteningCleanupMode::Disabled,
228 stringifyFlatteningCleanupMode(::llzk::polymorphic::FlatteningCleanupMode::Disabled),
229 "No definitions are deleted."),
230 clEnumValN(::llzk::polymorphic::FlatteningCleanupMode::Preimage,
231 stringifyFlatteningCleanupMode(::llzk::polymorphic::FlatteningCleanupMode::Preimage),
232 "Only definitions that were replaced with concrete instantiations are deleted."),
233 clEnumValN(::llzk::polymorphic::FlatteningCleanupMode::ConcreteAsRoot,
234 stringifyFlatteningCleanupMode(::llzk::polymorphic::FlatteningCleanupMode::ConcreteAsRoot),
235 "All definitions that cannot be reached by a use-def chain from some concrete definition are deleted."),
236 clEnumValN(::llzk::polymorphic::FlatteningCleanupMode::MainAsRoot,
237 stringifyFlatteningCleanupMode(::llzk::polymorphic::FlatteningCleanupMode::MainAsRoot),
238 "All definitions that cannot be reached by a use-def chain from the \"Main\" struct are deleted.")
239 )}]>,
240 ];
241}
242
243def WildcardArraySpecializationPass
244 : LLZKPass<"llzk-specialize-wildcard-arrays"> {
245 let summary =
246 "Refine wildcard array casts and specialize concrete call targets";
247 let description = [{
248 Refines `poly.unifiable_cast` results when wildcard `array.type` dimensions can be replaced
249 with concrete integer sizes from the input type. Then specializes free functions, extern
250 declarations, and whole structs for calls whose wildcard array dimensions have become concrete.
251
252 This pass is intended to run after `llzk-flatten`. It iterates to a fixpoint so newly-refined
253 cast result types can enable further callable specialization in later iterations.
254 }];
255 let options = [Option<"iterationLimit", "max-iter", "unsigned",
256 /* default */ "1000",
257 "Maximum number of iterations before the pass gives up "
258 "reaching a fixpoint.">];
259}
260
261def TypeVarInferencePass : LLZKPass<"llzk-infer-tvar"> {
262 let summary = "Infer concrete function types for polymorphic type variables";
263 let description = [{
264 Infers concrete replacements for `!poly.tvar` template parameters from operations in
265 `function.def` or `poly.expr` bodies, such as `poly.unifiable_cast` between a type variable
266 and a more concrete type (which means for maximum effect you should run it before other
267 optimization passes that can remove `poly.unifiable_cast`). Rewrites affected function
268 signatures and body types, removes redundant casts, drops resolved `poly.param` declarations,
269 and updates call-site template parameter lists.
270
271 The pass also creates template-local instantiated versions of functions for each
272 type-variable replacement. The `llzk-flatten` pass performs similar instantiation of type
273 variables but only as part of its more extensive lowering and flattening, all of which may
274 not be desired in some cases (hence the utility of this pass).
275
276 This pass may result in `poly.template` containing no `poly.param` or `poly.expr`. Run
277 `llzk-drop-empty-templates` after this pass to simplify these templates.
278 }];
279}
280
281#endif // LLZK_POLYMORPHIC_TRANSFORMATION_PASSES_TD