LLZK 2.1.1
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
Ops.td
Go to the documentation of this file.
1//===-- Ops.td ---------------------------------------------*- tablegen -*-===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
10#ifndef LLZK_VERIF_OPS
11#define LLZK_VERIF_OPS
12
13include "llzk/Dialect/Verif/IR/Dialect.td"
14include "llzk/Dialect/Verif/IR/OpInterfaces.td"
15include "mlir/Interfaces/CallInterfaces.td"
16include "mlir/Interfaces/FunctionInterfaces.td"
17include "llzk/Dialect/Shared/OpTraits.td"
18include "llzk/Dialect/Shared/Types.td"
19
20include "mlir/IR/OpAsmInterface.td"
21include "mlir/IR/OpBase.td"
22include "mlir/IR/SymbolInterfaces.td"
23
24class VerifDialectOp<string mnemonic, list<Trait> traits = []>
25 : Op<VerifDialect, mnemonic, traits>;
26
27class ConditionOp<string mnemonic, list<Trait> traits = []>
28 : VerifDialectOp<mnemonic,
29 traits#[ConditionOpInterface,
30 HasAncestor<"::llzk::verif::ContractOp">]> {
31
32 let arguments = (ins I1:$condition);
33 let results = (outs);
34
35 let assemblyFormat = [{ $condition attr-dict }];
36
37 let extraClassDefinition = [{
38 // This side effect models "program termination". Based on
39 // https://github.com/llvm/llvm-project/blob/f325e4b2d836d6e65a4d0cf3efc6b0996ccf3765/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp#L92-L97
40 void $cppClass::getEffects(
41 ::mlir::SmallVectorImpl<::mlir::SideEffects::EffectInstance<::mlir::MemoryEffects::Effect>> &effects
42 ) {
43 effects.emplace_back(::mlir::MemoryEffects::Write::get());
44 }
45 }];
46}
47
48//===------------------------------------------------------------------===//
49// Precondition operations
50//===------------------------------------------------------------------===//
51
52class RequireOpBase<string mnemonic, list<Trait> traits = []>
53 : ConditionOp<mnemonic, traits#[PreconditionOpInterface]> {
54 let description = [{
55 Base of an operation that encodes a precondition.
56
57 Preconditions:
58 - May not depend on values transitively derived from struct member reads
59 or from function return values.
60 - May not be present on the `llzk.main` entry struct.
61 }];
62}
63
64def RequireComputeOp : RequireOpBase<"require_compute"> {
65 let summary = "witness computation precondition";
66}
67
68def RequireConstrainOp : RequireOpBase<"require_constrain"> {
69 let summary = "constraint generation precondition";
70}
71
72//===------------------------------------------------------------------===//
73// Postcondition operations
74//===------------------------------------------------------------------===//
75
76class EnsureOpBase<string mnemonic, list<Trait> traits = []>
77 : ConditionOp<mnemonic, traits#[PostconditionOpInterface]>;
78
79def EnsureComputeOp : EnsureOpBase<"ensure_compute"> {
80 let summary = "witness computation postcondition";
81}
82
83def EnsureConstrainOp : EnsureOpBase<"ensure_constrain"> {
84 let summary = "constraint generation postcondition";
85}
86
87//===------------------------------------------------------------------===//
88// ContractDefOp
89//===------------------------------------------------------------------===//
90
91def ContractOp
92 : VerifDialectOp<
93 "contract",
94 [ParentOneOf<["::mlir::ModuleOp", "::llzk::polymorphic::TemplateOp"]>,
95 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
96 AutomaticAllocationScope, FunctionOpInterface,
97 SingleBlockImplicitTerminator<"::llzk::verif::ContractEndOp">,
98 IsolatedFromAbove]> {
99 let summary = "defines a specification contract for a given symbol";
100 let description = [{
101 A specification contract for a given function or struct in the circuit.
102 Contracts are "function-like", as they have arguments matching their targets,
103 and are "called" by the `verif.include` operation so that contracts can require
104 other contracts to hold on subcomponents, etc.
105
106 Contract argument rules:
107 - For function targets, the contract accepts arguments matching the function's arguments.
108 - For struct targets, the contract accepts arguments matching the argument list of the
109 struct's `@constrain` function.
110
111 Contracts do not return values. For function targets with return values, the
112 return values are appended to the end of the contract's argument list.
113
114 Contracts may not directly target struct functions; they must target the structs themselves.
115 Contracts targeting the module's `llzk.main` struct may not contain direct
116 `verif.require_compute` or `verif.require_constrain` operations in their body.
117
118 For templated targets (i.e., functions or structs within a `poly.template`),
119 the contract body may reference the target template's parameters and
120 expressions even if the contract is physically nested elsewhere.
121
122 Examples:
123
124 ```llzk
125
126 struct.def @Bar {
127 function.def @compute(%in : !felt.type) -> !struct.type<@Bar> { ... }
128 function.def @constrain(%self : !struct.type<@Bar>, %in : !felt.type) { ... }
129 }
130
131 verif.contract @FooContract for @Bar (%self : !struct.type<@Bar>, %in : !felt.type) {
132 ...
133 }
134
135 function.def @free_func (%a : !felt.type) -> !felt.type { ... }
136
137 verif.contract @BarContract for @free_func (%a : !felt.type, %ret_value : !felt.type) {
138 ...
139 }
140
141 ```
142 }];
143
144 let arguments = (ins SymbolNameAttr:$sym_name, SymbolRefAttr:$target,
145 TypeAttrOf<FunctionType>:$function_type,
146 OptionalAttr<DictArrayAttr>:$arg_attrs);
147
148 let regions = (region AnyRegion:$body);
149
150 let hasCustomAssemblyFormat = 1;
151 let hasVerifier = 1;
152 let hasRegionVerifier = 1;
153
154 // Define builders manually so builder-created contracts always contain a
155 // complete single-block body with the implicit `verif.contract_end`.
156 let skipDefaultBuilders = 1;
157 let builders = [OpBuilder<(ins "::mlir::StringAttr":$sym_name,
158 "::mlir::SymbolRefAttr":$target,
159 "::mlir::TypeAttr":$function_type,
160 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
161 [{
162 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
163 }]>,
164 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
165 "::mlir::StringAttr":$sym_name,
166 "::mlir::SymbolRefAttr":$target,
167 "::mlir::TypeAttr":$function_type,
168 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
169 [{
170 $_state.getOrAddProperties<Properties>().sym_name = sym_name;
171 $_state.getOrAddProperties<Properties>().target = target;
172 $_state.getOrAddProperties<Properties>().function_type = function_type;
173 if (arg_attrs) {
174 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
175 }
176 initializeEmptyBody(
177 $_builder, $_state, ::mlir::cast<::mlir::FunctionType>(function_type.getValue())
178 );
179 assert(resultTypes.size() == 0u && "mismatched number of results");
180 $_state.addTypes(resultTypes);
181 }]>,
182 OpBuilder<(ins "::llvm::StringRef":$sym_name,
183 "::mlir::SymbolRefAttr":$target,
184 "::mlir::FunctionType":$function_type,
185 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
186 [{
187 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
188 }]>,
189 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
190 "::llvm::StringRef":$sym_name,
191 "::mlir::SymbolRefAttr":$target,
192 "::mlir::FunctionType":$function_type,
193 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
194 [{
195 $_state.getOrAddProperties<Properties>().sym_name = $_builder.getStringAttr(sym_name);
196 $_state.getOrAddProperties<Properties>().target = target;
197 $_state.getOrAddProperties<Properties>().function_type = ::mlir::TypeAttr::get(function_type);
198 if (arg_attrs) {
199 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
200 }
201 initializeEmptyBody($_builder, $_state, function_type);
202 assert(resultTypes.size() == 0u && "mismatched number of results");
203 $_state.addTypes(resultTypes);
204 }]>,
205 OpBuilder<(ins "::llvm::StringRef":$name,
206 "llvm::StringRef":$target)>,
207 OpBuilder<(ins "::llvm::StringRef":$name,
208 "::mlir::SymbolRefAttr":$target)>];
209
210 let extraClassDeclaration = [{
211 /// Create a deep copy of this contract and all of its blocks, remapping any
212 /// operands that use values outside of the contract using the map that is
213 /// provided (leaving them alone if no entry is present). If the mapper
214 /// contains entries for contract arguments, these arguments are not
215 /// included in the new contract. Replaces references to cloned sub-values
216 /// with the corresponding value that is copied, and adds those mappings to
217 /// the mapper.
218 ContractOp clone(::mlir::IRMapping &mapper);
219 ContractOp clone();
220
221 /// Clone the internal blocks and attributes from this contract into dest.
222 /// Any cloned blocks are appended to the back of dest. This contract
223 /// asserts that the attributes of the current contract and dest are
224 /// compatible.
225 void cloneInto(ContractOp dest, ::mlir::IRMapping &mapper);
226
227 /// Return `true` iff the argument at the given index has `pub` attribute.
228 bool hasArgPublicAttr(unsigned index);
229
230 /// Return `true` iff the argument at the given index has a `function.arg_name` attribute.
231 bool hasArgName(unsigned index);
232
233 /// Return the `function.arg_name` attribute for the argument at the given index.
234 ::std::optional<::mlir::StringAttr> getArgNameAttr(unsigned index);
235
236 /// Set the `function.arg_name` attribute for the argument at the given index.
237 void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr);
238
239 /// Set the `function.arg_name` attribute for the argument at the given index from a string.
240 void setArgName(unsigned index, ::llvm::StringRef name);
241
242 /// Required by FunctionOpInterface.
243 /// Returns the region on the current operation that is callable.
244 ::mlir::Region *getCallableRegion() { return &getBody(); }
245
246 /// Required by FunctionOpInterface.
247 /// Returns the argument types of this contract.
248 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
249
250 /// Required by FunctionOpInterface.
251 /// Returns the result types of this contract. Since contracts don't have
252 /// return values, the returned ArrayRef will always be empty.
253 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
254
255 /// Required by SymbolOpInterface.
256 bool isDeclaration() { return false; }
257
258 /// Return the full name for this contract from the root module, including
259 /// all surrounding symbol table names (i.e., modules and structs).
260 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
261
262 /// Return `true` iff the contract targets a struct type.
263 bool hasStructTarget() { return succeeded(getStructTarget()); }
264
265 /// Return the StructDefOp that this contract targets, or failure if it does not
266 /// target a struct or the struct is not found.
267 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget(::mlir::SymbolTableCollection &tables);
268
269 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget() {
270 ::mlir::SymbolTableCollection tables;
271 return getStructTarget(tables);
272 }
273
274 /// Return the "self" value (i.e. the first parameter) from the contract, or
275 /// failure if the contract does not target a struct.
276 ::mlir::FailureOr<::mlir::Value> getSelfValue();
277
278 /// Return `true` iff the contract targets a function.
279 bool hasFuncTarget() { return succeeded(getFuncTarget()); }
280
281 /// Return the FuncDefOp that this contract targets, or failure if it does not
282 /// target a function or the function is not found.
283 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget(::mlir::SymbolTableCollection &tables);
284
285 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget() {
286 ::mlir::SymbolTableCollection tables;
287 return getFuncTarget(tables);
288 }
289
290 private:
291 /// Populate a builder-created contract body with one entry block matching
292 /// the function signature and insert the implicit `verif.contract_end`.
293 static void initializeEmptyBody(
294 ::mlir::OpBuilder &builder, ::mlir::OperationState &state,
295 ::mlir::FunctionType functionType
296 );
297 }];
298}
299
300//===------------------------------------------------------------------===//
301// ContractEndOp
302//===------------------------------------------------------------------===//
303
304def ContractEndOp
305 : VerifDialectOp<"contract_end", [Pure, Terminator,
306 HasParent<"::llzk::verif::ContractOp">]> {
307 let summary = "terminates a verif.contract body";
308 let description = [{
309 Implicit terminator for `verif.contract` regions. This operation is inserted
310 automatically by the parser and omitted by the custom printer so contract
311 syntax remains terminator-free.
312
313 A terminator is expected by certain MLIR utilities (e.g., the data-flow analysis
314 framework), which is why this op is added.
315 }];
316
317 let assemblyFormat = "attr-dict";
318}
319
320//===------------------------------------------------------------------===//
321// IncludeOp
322//===------------------------------------------------------------------===//
323
324def IncludeOp
325 : VerifDialectOp<
326 "include", [MemRefsNormalizable, AttrSizedOperandSegments,
327 VerifySizesForMultiAffineOps<1>,
328 DeclareOpInterfaceMethods<CallOpInterface>,
329 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
330 HasAncestor<"::llzk::verif::ContractOp">]> {
331 let summary = "contract inclusion operation";
332 let description = [{
333 Invokes another specification contract from another contract, effectively including the
334 specifications from another specification into the current contract.
335 }];
336
337 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
338 let arguments = (ins
339 // Call target contract reference.
340 SymbolRefAttr:$callee,
341 // List of arguments to call the target contract.
342 Variadic<AnyLLZKType>:$argOperands,
343 // List of parameters to instantiate all `poly.param` symbols when the
344 // callee is a contract inside a `poly.template` region.
345 OptionalAttr<ArrayAttr>:$templateParams,
346 // List of AffineMap operand groups where each group provides the
347 // arguments to instantiate the next (left-to-right) AffineMap used as a
348 // struct parameter in the result StructType.
349 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
350 // Within each group in '$mapOperands', denotes the number of values that
351 // are AffineMap "dimensional" arguments with the remaining values being
352 // AffineMap "symbolic" arguments.
353 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
354 // Denotes the size of each variadic group in '$mapOperands'.
355 DenseI32ArrayAttr:$mapOpGroupSizes);
356
357 let assemblyFormat = [{
358 $callee
359 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
360 `` `(` $argOperands `)`
361 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
362 `:` functional-type($argOperands, results)
363 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
364 }];
365
366 // Define builders manually so inference of operand layout attributes is not
367 // circumvented.
368 let skipDefaultBuilders = 1;
369 let builders =
370 [OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
371 CArg<"::mlir::ValueRange", "{}">:$argOperands,
372 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
373 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
374 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
375 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
376 CArg<"::mlir::ValueRange", "{}">:$argOperands,
377 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
378 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
379 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
380 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
381 CArg<"::mlir::ValueRange", "{}">:$argOperands,
382 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
383 "{}">:$templateParams),
384 [{
385 build($_builder, $_state, callee, mapOperands,
386 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
387 argOperands, templateParams);
388 }]>,
389 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
390 CArg<"::mlir::ValueRange", "{}">:$argOperands,
391 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
392 "{}">:$templateParams),
393 [{
394 build($_builder, $_state,
395 callee.getFullyQualifiedName(false),
396 argOperands, templateParams);
397 }]>,
398 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
399 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
400 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
401 CArg<"::mlir::ValueRange", "{}">:$argOperands,
402 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
403 "{}">:$templateParams),
404 [{
405 build($_builder, $_state,
406 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
407 argOperands, templateParams);
408 }]>,
409 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
410 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
411 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
412 CArg<"::mlir::ValueRange", "{}">:$argOperands,
413 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
414 "{}">:$templateParams),
415 [{
416 build($_builder, $_state, callee, mapOperands,
417 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
418 argOperands, templateParams);
419 }]>];
420
421 let extraClassDeclaration = [{
422 /// Required by CallOpInterface
423 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
424
425 /// Required by CallOpInterface
426 ::mlir::Operation *resolveCallable();
427
428 /// Return the FunctionType inferred from the arg operands of this CallOp.
429 /// This is not necessarily the same as the callee's FunctionType but should unify with it
430 /// or else IR verification will fail.
431 ::mlir::FunctionType getTypeSignature();
432
433 /// Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and
434 /// the given FunctionType (as RHS). If successful, return a UnificationMap containing the
435 /// unifications that were made. Otherwise, return failure.
436 ::mlir::FailureOr<UnificationMap> unifyTypeSignature(::mlir::FunctionType other);
437
438 /// Return `true` iff the contract targets a struct type.
439 bool contractTargetsStruct();
440
441 /// Return the "self" value (i.e. the first parameter) from the callee contract,
442 /// assuming the target of the contract is a struct target.
443 ::mlir::Value getSelfValue();
444
445 /// Resolve and return the target Contract for this CallOp.
446 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::verif::ContractOp>>
447 getCalleeTarget(::mlir::SymbolTableCollection &tables);
448
449 /// Allocate consecutive storage of the ValueRange instances in the parameter
450 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
451 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
452
453 /// Check type compatibility of the given template parameter value from this `CallOp` against
454 /// the declared type on the given `TemplateParamOp` (if any).
455 ::mlir::LogicalResult verifyTemplateParamCompatibility(
456 ::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam
457 );
458
459 /// Check type compatibility of each template parameter value provided in this `CallOp` against
460 /// the declared type on each `TemplateParamOp` (if any).
461 ///
462 /// Pre-condition assertions:
463 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
464 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
465 ::mlir::LogicalResult verifyTemplateParamCompatibility(
466 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs
467 );
468
469 /// Verify that each template parameter value provided in this `CallOp` is consistent with
470 /// the value inferred for the target `TemplateParamOp` in the given `UnificationMap`. The
471 /// `UnificationMap` is expected to contain the unification results of this `CallOp` against
472 /// the target function type signature.
473 ///
474 /// Pre-condition assertions:
475 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
476 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
477 ::mlir::LogicalResult verifyTemplateParamsMatchInferred(
478 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs,
479 const UnificationMap &unifications
480 );
481 }];
482}
483
484#endif // LLZK_VERIF_OPS