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