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