LLZK 3.0.0
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 "llzk/Dialect/SMT/IR/SMTTypes.td"
16include "mlir/Interfaces/CallInterfaces.td"
17include "mlir/Interfaces/FunctionInterfaces.td"
18include "llzk/Dialect/Shared/OpTraits.td"
19include "llzk/Dialect/Shared/Types.td"
20include "llzk/Dialect/Felt/IR/Types.td"
21
22include "mlir/IR/OpAsmInterface.td"
23include "mlir/IR/OpBase.td"
24include "mlir/IR/SymbolInterfaces.td"
25
26class VerifDialectOp<string mnemonic, list<Trait> traits = []>
27 : Op<VerifDialect, mnemonic, traits>;
28
29class ConditionOp<string mnemonic, list<Trait> traits = []>
30 : VerifDialectOp<mnemonic, traits#[ConditionOpInterface]> {
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
48class ContractConditionOp<string mnemonic, list<Trait> traits = []>
49 : ConditionOp<mnemonic, traits#[HasAncestor<"::llzk::verif::ContractOp">]>;
50
51//===------------------------------------------------------------------===//
52// Struct verification condition operations
53//===------------------------------------------------------------------===//
54
55def VerifAssertOp : ConditionOp<"assert", []> {
56 let summary = "An assertion in the target of a contract, derived from one of "
57 "the contract's specifications";
58 let description = summary;
59}
60
61def VerifProveOp : ConditionOp<"prove", []> {
62 let summary = "A proof obligation in the target of a contract, derived from"
63 "one of the contract's specifications";
64 let description = summary;
65}
66
67def VerifSMTProveOp : Op<VerifDialect, "smt_prove", []> {
68 let arguments = (ins BoolType:$condition);
69 let summary = "A lowered proof obligation over an SMT expression";
70 let assemblyFormat = [{ $condition attr-dict }];
71}
72
73//===------------------------------------------------------------------===//
74// Precondition operations
75//===------------------------------------------------------------------===//
76
77class RequireOpBase<string mnemonic, list<Trait> traits = []>
78 : ContractConditionOp<mnemonic, traits#[PreconditionOpInterface]> {
79 let description = [{
80 Base of an operation that encodes a precondition.
81
82 Preconditions:
83 - May not depend on values transitively derived from struct member reads
84 or from function return values.
85 - May not be present on the `llzk.main` entry struct.
86 }];
87}
88
89def RequireComputeOp : RequireOpBase<"require_compute"> {
90 let summary = "witness computation precondition";
91}
92
93def RequireConstrainOp : RequireOpBase<"require_constrain"> {
94 let summary = "constraint generation precondition";
95}
96
97//===------------------------------------------------------------------===//
98// Postcondition operations
99//===------------------------------------------------------------------===//
100
101class EnsureOpBase<string mnemonic, list<Trait> traits = []>
102 : ContractConditionOp<mnemonic, traits#[PostconditionOpInterface]>;
103
104def EnsureComputeOp : EnsureOpBase<"ensure_compute"> {
105 let summary = "witness computation postcondition";
106}
107
108def EnsureConstrainOp : EnsureOpBase<"ensure_constrain"> {
109 let summary = "constraint generation postcondition";
110}
111
112//===------------------------------------------------------------------===//
113// ContractDefOp
114//===------------------------------------------------------------------===//
115
116def ContractOp
117 : VerifDialectOp<
118 "contract",
119 [ParentOneOf<["::mlir::ModuleOp", "::llzk::polymorphic::TemplateOp"]>,
120 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
121 AutomaticAllocationScope, FunctionOpInterface,
122 SingleBlockImplicitTerminator<"::llzk::verif::ContractEndOp">,
123 IsolatedFromAbove]> {
124 let summary = "defines a specification contract for a given symbol";
125 let description = [{
126 A specification contract for a given function or struct in the circuit.
127 Contracts are "function-like", as they have arguments matching their targets,
128 and are "called" by the `verif.include` operation so that contracts can require
129 other contracts to hold on subcomponents, etc.
130
131 Contract argument rules:
132 - For function targets, the contract accepts arguments matching the function's arguments.
133 - For struct targets, the contract accepts arguments matching the argument list of the
134 struct's `@constrain` function.
135
136 Contracts do not return values. For function targets with return values, the
137 return values are appended to the end of the contract's argument list.
138
139 Contracts may not directly target struct functions; they must target the structs themselves.
140 Contracts targeting the module's `llzk.main` struct may contain `verif.require_compute`
141 or `verif.require_constrain` operations. A circuit whose main-level contract contains such
142 preconditions is valid only for inputs that satisfy them.
143
144 For templated targets (i.e., functions or structs within a `poly.template`), the contract
145 body may reference the target template's parameters and expressions. If a contract is nested
146 inside a `poly.template`, then the target must be in that same template.
147
148 Examples:
149
150 ```llzk
151
152 struct.def @Bar {
153 function.def @compute(%in : !felt.type) -> !struct.type<@Bar> { ... }
154 function.def @constrain(%self : !struct.type<@Bar>, %in : !felt.type) { ... }
155 }
156
157 verif.contract @FooContract for @Bar (%self : !struct.type<@Bar>, %in : !felt.type) {
158 ...
159 }
160
161 function.def @free_func (%a : !felt.type) -> !felt.type { ... }
162
163 verif.contract @BarContract for @free_func (%a : !felt.type, %ret_value : !felt.type) {
164 ...
165 }
166
167 ```
168 }];
169
170 let arguments = (ins SymbolNameAttr:$sym_name, SymbolRefAttr:$target,
171 TypeAttrOf<FunctionType>:$function_type,
172 OptionalAttr<DictArrayAttr>:$arg_attrs);
173
174 let regions = (region AnyRegion:$body);
175
176 let hasCustomAssemblyFormat = 1;
177 let hasVerifier = 1;
178 let hasRegionVerifier = 1;
179
180 // Define builders manually so builder-created contracts always contain a
181 // complete single-block body with the implicit `verif.contract_end`.
182 let skipDefaultBuilders = 1;
183 let builders = [OpBuilder<(ins "::mlir::StringAttr":$sym_name,
184 "::mlir::SymbolRefAttr":$target,
185 "::mlir::TypeAttr":$function_type,
186 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
187 [{
188 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
189 }]>,
190 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
191 "::mlir::StringAttr":$sym_name,
192 "::mlir::SymbolRefAttr":$target,
193 "::mlir::TypeAttr":$function_type,
194 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
195 [{
196 $_state.getOrAddProperties<Properties>().sym_name = sym_name;
197 $_state.getOrAddProperties<Properties>().target = target;
198 $_state.getOrAddProperties<Properties>().function_type = function_type;
199 if (arg_attrs) {
200 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
201 }
202 initializeEmptyBody(
203 $_builder, $_state, ::mlir::cast<::mlir::FunctionType>(function_type.getValue())
204 );
205 assert(resultTypes.size() == 0u && "mismatched number of results");
206 $_state.addTypes(resultTypes);
207 }]>,
208 OpBuilder<(ins "::llvm::StringRef":$sym_name,
209 "::mlir::SymbolRefAttr":$target,
210 "::mlir::FunctionType":$function_type,
211 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
212 [{
213 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
214 }]>,
215 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
216 "::llvm::StringRef":$sym_name,
217 "::mlir::SymbolRefAttr":$target,
218 "::mlir::FunctionType":$function_type,
219 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
220 [{
221 $_state.getOrAddProperties<Properties>().sym_name = $_builder.getStringAttr(sym_name);
222 $_state.getOrAddProperties<Properties>().target = target;
223 $_state.getOrAddProperties<Properties>().function_type = ::mlir::TypeAttr::get(function_type);
224 if (arg_attrs) {
225 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
226 }
227 initializeEmptyBody($_builder, $_state, function_type);
228 assert(resultTypes.size() == 0u && "mismatched number of results");
229 $_state.addTypes(resultTypes);
230 }]>,
231 OpBuilder<(ins "::llvm::StringRef":$name,
232 "llvm::StringRef":$target)>,
233 OpBuilder<(ins "::llvm::StringRef":$name,
234 "::mlir::SymbolRefAttr":$target)>];
235
236 let extraClassDeclaration = [{
237 /// Create a deep copy of this contract and all of its blocks, remapping any
238 /// operands that use values outside of the contract using the map that is
239 /// provided (leaving them alone if no entry is present). If the mapper
240 /// contains entries for contract arguments, these arguments are not
241 /// included in the new contract. Replaces references to cloned sub-values
242 /// with the corresponding value that is copied, and adds those mappings to
243 /// the mapper.
244 ContractOp clone(::mlir::IRMapping &mapper);
245 ContractOp clone();
246
247 /// Clone the internal blocks and attributes from this contract into dest.
248 /// Any cloned blocks are appended to the back of dest. This contract
249 /// asserts that the attributes of the current contract and dest are
250 /// compatible.
251 void cloneInto(ContractOp dest, ::mlir::IRMapping &mapper);
252
253 /// Return `true` iff the argument at the given index has `pub` attribute.
254 bool hasArgPublicAttr(unsigned index);
255
256 /// Return `true` iff the argument at the given index has a `function.arg_name` attribute.
257 bool hasArgName(unsigned index);
258
259 /// Return the `function.arg_name` attribute for the argument at the given index.
260 ::std::optional<::mlir::StringAttr> getArgNameAttr(unsigned index);
261
262 /// Set the `function.arg_name` attribute for the argument at the given index.
263 void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr);
264
265 /// Set the `function.arg_name` attribute for the argument at the given index from a string.
266 void setArgName(unsigned index, ::llvm::StringRef name);
267
268 /// Required by FunctionOpInterface.
269 /// Returns the region on the current operation that is callable.
270 ::mlir::Region *getCallableRegion() { return &getBody(); }
271
272 /// Required by FunctionOpInterface.
273 /// Returns the argument types of this contract.
274 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
275
276 /// Required by FunctionOpInterface.
277 /// Returns the result types of this contract. Since contracts don't have
278 /// return values, the returned ArrayRef will always be empty.
279 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
280
281 /// Required by SymbolOpInterface.
282 bool isDeclaration() { return false; }
283
284 /// Return the full name for this contract from the root module, including
285 /// all surrounding symbol table names (i.e., modules and structs).
286 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
287
288 /// Return `true` iff the contract targets a struct type.
289 bool hasStructTarget() { return succeeded(getStructTarget()); }
290
291 /// Return the StructDefOp that this contract targets, or failure if it does not
292 /// target a struct or the struct is not found.
293 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget(::mlir::SymbolTableCollection &tables);
294
295 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget() {
296 ::mlir::SymbolTableCollection tables;
297 return getStructTarget(tables);
298 }
299
300 /// Return the "self" value (i.e. the first parameter) from the contract, or
301 /// failure if the contract does not target a struct.
302 ::mlir::FailureOr<::mlir::Value> getSelfValue();
303
304 /// Return `true` iff the contract targets a function.
305 bool hasFuncTarget() { return succeeded(getFuncTarget()); }
306
307 /// Return the FuncDefOp that this contract targets, or failure if it does not
308 /// target a function or the function is not found.
309 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget(::mlir::SymbolTableCollection &tables);
310
311 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget() {
312 ::mlir::SymbolTableCollection tables;
313 return getFuncTarget(tables);
314 }
315
316 /// Return the operation that this contract targets, or failure if it does not
317 /// target an operation that implements the `ContractTarget` op interface or is not found.
318 ::mlir::FailureOr<SymbolLookupResult<::llzk::verif::ContractTargetOpInterface>> getTargetOp(::mlir::SymbolTableCollection &tables);
319
320 ::mlir::FailureOr<SymbolLookupResult<::llzk::verif::ContractTargetOpInterface>> getTargetOp() {
321 ::mlir::SymbolTableCollection tables;
322 return getTargetOp(tables);
323 }
324
325 private:
326 /// Populate a builder-created contract body with one entry block matching
327 /// the function signature and insert the implicit `verif.contract_end`.
328 static void initializeEmptyBody(
329 ::mlir::OpBuilder &builder, ::mlir::OperationState &state,
330 ::mlir::FunctionType functionType
331 );
332 }];
333}
334
335//===------------------------------------------------------------------===//
336// ContractEndOp
337//===------------------------------------------------------------------===//
338
339def ContractEndOp
340 : VerifDialectOp<"contract_end", [Pure, Terminator,
341 HasParent<"::llzk::verif::ContractOp">]> {
342 let summary = "terminates a verif.contract body";
343 let description = [{
344 Implicit terminator for `verif.contract` regions. This operation is inserted
345 automatically by the parser and omitted by the custom printer so contract
346 syntax remains terminator-free.
347
348 A terminator is expected by certain MLIR utilities (e.g., the data-flow analysis
349 framework), which is why this op is added.
350 }];
351
352 let assemblyFormat = "attr-dict";
353}
354
355//===------------------------------------------------------------------===//
356// IncludeOp
357//===------------------------------------------------------------------===//
358
359def IncludeOp
360 : VerifDialectOp<
361 "include", [MemRefsNormalizable, AttrSizedOperandSegments,
362 VerifySizesForMultiAffineOps<1>,
363 DeclareOpInterfaceMethods<CallOpInterface>,
364 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
365 HasAncestor<"::llzk::verif::ContractOp">]> {
366 let summary = "contract inclusion operation";
367 let description = [{
368 Invokes another specification contract from another contract, effectively including the
369 specifications from another specification into the current contract.
370 }];
371
372 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
373 let arguments = (ins
374 // Call target contract reference.
375 SymbolRefAttr:$callee,
376 // List of arguments to call the target contract.
377 Variadic<AnyLLZKType>:$argOperands,
378 // List of parameters to instantiate all `poly.param` symbols when the
379 // callee is a contract inside a `poly.template` region.
380 OptionalAttr<ArrayAttr>:$templateParams,
381 // List of AffineMap operand groups where each group provides the
382 // arguments to instantiate the next (left-to-right) AffineMap used as a
383 // struct parameter in the result StructType.
384 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
385 // Within each group in '$mapOperands', denotes the number of values that
386 // are AffineMap "dimensional" arguments with the remaining values being
387 // AffineMap "symbolic" arguments.
388 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
389 // Denotes the size of each variadic group in '$mapOperands'.
390 DenseI32ArrayAttr:$mapOpGroupSizes);
391
392 let assemblyFormat = [{
393 $callee
394 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
395 `` `(` $argOperands `)`
396 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
397 `:` functional-type($argOperands, results)
398 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
399 }];
400
401 // Define builders manually so inference of operand layout attributes is not
402 // circumvented.
403 let skipDefaultBuilders = 1;
404 let builders =
405 [OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
406 CArg<"::mlir::ValueRange", "{}">:$argOperands,
407 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
408 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
409 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
410 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
411 CArg<"::mlir::ValueRange", "{}">:$argOperands,
412 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
413 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
414 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
415 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
416 CArg<"::mlir::ValueRange", "{}">:$argOperands,
417 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
418 "{}">:$templateParams),
419 [{
420 build($_builder, $_state, callee, mapOperands,
421 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
422 argOperands, templateParams);
423 }]>,
424 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
425 CArg<"::mlir::ValueRange", "{}">:$argOperands,
426 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
427 "{}">:$templateParams),
428 [{
429 build($_builder, $_state,
430 callee.getFullyQualifiedName(false),
431 argOperands, templateParams);
432 }]>,
433 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
434 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
435 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
436 CArg<"::mlir::ValueRange", "{}">:$argOperands,
437 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
438 "{}">:$templateParams),
439 [{
440 build($_builder, $_state,
441 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
442 argOperands, templateParams);
443 }]>,
444 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
445 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
446 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
447 CArg<"::mlir::ValueRange", "{}">:$argOperands,
448 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
449 "{}">:$templateParams),
450 [{
451 build($_builder, $_state, callee, mapOperands,
452 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
453 argOperands, templateParams);
454 }]>];
455
456 let extraClassDeclaration = [{
457 /// Required by CallOpInterface
458 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
459
460 /// Required by CallOpInterface
461 ::mlir::Operation *resolveCallable();
462
463 /// Return the FunctionType inferred from the arg operands of this CallOp.
464 /// This is not necessarily the same as the callee's FunctionType but should unify with it
465 /// or else IR verification will fail.
466 ::mlir::FunctionType getTypeSignature();
467
468 /// Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and
469 /// the given FunctionType (as RHS). If successful, return a UnificationMap containing the
470 /// unifications that were made. Otherwise, return failure.
471 ::mlir::FailureOr<UnificationMap> unifyTypeSignature(::mlir::FunctionType other);
472
473 /// Return `true` iff the contract targets a struct type.
474 bool contractTargetsStruct();
475
476 /// Return the "self" value (i.e. the first parameter) from the callee contract,
477 /// assuming the target of the contract is a struct target.
478 ::mlir::Value getSelfValue();
479
480 /// Resolve and return the target Contract for this CallOp.
481 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::verif::ContractOp>>
482 getCalleeTarget(::mlir::SymbolTableCollection &tables);
483
484 /// Allocate consecutive storage of the ValueRange instances in the parameter
485 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
486 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
487
488 /// Check type compatibility of the given template parameter value from this `CallOp` against
489 /// the declared type on the given `TemplateParamOp` (if any).
490 ::mlir::LogicalResult verifyTemplateParamCompatibility(
491 ::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam
492 );
493
494 /// Check type compatibility of each template parameter value provided in this `CallOp` against
495 /// the declared type on each `TemplateParamOp` (if any).
496 ///
497 /// Pre-condition assertions:
498 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
499 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
500 ::mlir::LogicalResult verifyTemplateParamCompatibility(
501 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs
502 );
503
504 /// Verify that each template parameter value provided in this `CallOp` is consistent with
505 /// the value inferred for the target `TemplateParamOp` in the given `UnificationMap`. The
506 /// `UnificationMap` is expected to contain the unification results of this `CallOp` against
507 /// the target function type signature.
508 ///
509 /// Pre-condition assertions:
510 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
511 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
512 ::mlir::LogicalResult verifyTemplateParamsMatchInferred(
513 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs,
514 const UnificationMap &unifications
515 );
516 }];
517}
518
519//===------------------------------------------------------------------===//
520// InvariantOp
521//===------------------------------------------------------------------===//
522
523def InvariantOp
524 : VerifDialectOp<"invariant", [HasAncestor<"::llzk::verif::ContractOp">,
525 NoTerminator, SingleBlock]> {
526
527 let summary = "loop invariant definition operation";
528 let description = [{
529 Defines an invariant for a loop inside the target of a contract.
530
531 The targeted loop op must implement the `InvariantTargetOpInterface`.
532 This interface is already implemented by the `scf.while` and `scf.for`
533 operations. The loop name is defined on either of those ops with a
534 string attribute named `loop_label`.
535
536 The arguments of the body must match those declared by the target op.
537 In the case of `scf.for` the control values must also be block arguments
538 in the following order: lower bound, induction variable, upper bound, and stride.
539
540 Example:
541 ```
542 module attributes {llzk.lang} {
543 struct.def @Top {
544 function.def @compute() -> !struct.type<@Top> {
545 %self = struct.new : !struct.type<@Top>
546 %c0 = arith.constant 0 : index
547 %c10 = arith.constant 10 : index
548 %c1 = arith.constant 1 : index
549 %0 = felt.const 5
550 %1 = scf.for %iv = %c0 to %c10 step %c1 iter_args(%2 = %0) -> !felt.type {
551 scf.yield %2 : !felt.type
552 } {loop_label = "loopA"}
553 function.return %self : !struct.type<@Top>
554 }
555 function.def @constrain(%self: !struct.type<@Top>) {
556 function.return
557 }
558 }
559
560 verif.contract @Foo for @Top (%self: !struct.type<@Top>) {
561 verif.invariant for @loopA(%lb: index, %iv: index, %ub: index, %step: index, %extra: !felt.type) {
562 %true = arith.constant true
563 verif.require_compute %true
564 }
565 }
566 }
567 ```
568 }];
569
570 let arguments = (ins StrAttr:$loop_name, TypeArrayAttr:$loop_arg_types);
571
572 let regions = (region SizedRegion<1>:$region);
573
574 let skipDefaultBuilders = 1;
575 let builders = [OpBuilder<(ins "::mlir::StringRef":$loop_name,
576 CArg<"::llvm::ArrayRef<::mlir::Type>", "{}">:$loop_arg_types,
577 CArg<"::llvm::ArrayRef<::mlir::Location>", "{}">:$loop_arg_locs)>];
578
579 let hasCustomAssemblyFormat = 1;
580 let hasVerifier = 1;
581
582 let extraClassDeclaration = [{
583 /// Returns the contract operation that contains this invariant.
584 ::llzk::verif::ContractOp getParentContract();
585 /// Returns the loop target.
586 ::mlir::FailureOr<::llzk::verif::InvariantTargetOpInterface> getTarget();
587 }];
588}
589
590//===------------------------------------------------------------------===//
591// Invariant inner ops
592//===------------------------------------------------------------------===//
593
594class InvariantInnerOp<string mnemonic, list<Trait> traits = []>
595 : VerifDialectOp<
596 mnemonic,
597 traits#[HasAncestor<"::llzk::verif::InvariantOp">,
598 DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
599
600 let summary = mnemonic#" value operation";
601 let description = [{
602 Indicates that a value }]#mnemonic#[{ on each iteration of the loop.
603
604 This declares the `MemoryEffectsOpInterface`, which, like the `cf.assert` (MLIR `cf` dialect)
605 and `bool.assert` (LLZK `bool` dialect) ops, adds a MemWrite affect to model program termination.
606 }];
607
608 let arguments = (ins LLZK_FeltType:$value);
609
610 let extraClassDefinition = [{
611 // This side effect models "program termination". Based on
612 // https://github.com/llvm/llvm-project/blob/f325e4b2d836d6e65a4d0cf3efc6b0996ccf3765/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp#L92-L97
613 void $cppClass::getEffects(
614 ::mlir::SmallVectorImpl<::mlir::SideEffects::EffectInstance<::mlir::MemoryEffects::Effect>> &effects
615 ) {
616 effects.emplace_back(::mlir::MemoryEffects::Write::get());
617 }
618 }];
619
620 let assemblyFormat = "$value attr-dict";
621}
622
623def IncreasesOp : InvariantInnerOp<"increases"> {}
624
625def DecreasesOp : InvariantInnerOp<"decreases"> {}
626
627def StepOp : VerifDialectOp<
628 "step", [HasAncestor<"::llzk::verif::InvariantOp">,
629 DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
630 NoRegionArguments, SingleBlock]> {
631 let summary = "step predicate operation";
632 let description = [{
633 Defines a predicate that must be satisfied between iterations of the loop.
634
635 The predicate can access the value in the previous iteration by using the `verif.old` operation.
636
637 Declares the `MemoryEffectsOpInterface`, which, like the `cf.assert` (MLIR `cf` dialect)
638 and `bool.assert` (LLZK `bool` dialect) ops, adds a MemWrite affect to model program termination.
639 }];
640
641 let regions = (region SizedRegion<1>:$region);
642
643 let extraClassDefinition = [{
644 // This side effect models "program termination". Based on
645 // https://github.com/llvm/llvm-project/blob/f325e4b2d836d6e65a4d0cf3efc6b0996ccf3765/mlir/lib/Dialect/ControlFlow/IR/ControlFlowOps.cpp#L92-L97
646 void $cppClass::getEffects(
647 ::mlir::SmallVectorImpl<::mlir::SideEffects::EffectInstance<::mlir::MemoryEffects::Effect>> &effects
648 ) {
649 effects.emplace_back(::mlir::MemoryEffects::Write::get());
650 }
651 }];
652
653 let assemblyFormat = "$region attr-dict";
654}
655
656def StepYieldOp
657 : VerifDialectOp<"step.yield", [HasParent<"StepOp">, Terminator]> {
658 let summary = "step yield operation";
659 let description = [{
660 Terminator operation for `verif.step` blocks. Takes the boolean value carrying the predicate's result.
661 }];
662
663 let arguments = (ins I1:$value);
664
665 let assemblyFormat = "$value attr-dict";
666}
667
668def OldOp : VerifDialectOp<"old", [Pure, HasAncestor<"::llzk::verif::StepOp">,
669 AllTypesMatch<["value", "result"]>]> {
670 let summary = "old operation";
671 let description = [{
672 This operation allows accessing the value of an expression in the previous iteration of the loop.
673
674 In can only be used within the body of a `verif.step` operation.
675 }];
676
677 let arguments = (ins AnyLLZKType:$value);
678 let results = (outs AnyLLZKType:$result);
679
680 let assemblyFormat = "$value `:` type($result) attr-dict";
681}
682
683#endif // LLZK_VERIF_OPS