1//===-- Ops.td ---------------------------------------------*- tablegen -*-===//
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
8//===----------------------------------------------------------------------===//
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"
23include "mlir/IR/OpAsmInterface.td"
24include "mlir/IR/OpBase.td"
25include "mlir/IR/SymbolInterfaces.td"
27class VerifDialectOp<string mnemonic, list<Trait> traits = []>
28 : Op<VerifDialect, mnemonic, traits>;
30class SideEffectsOp<string mnemonic, list<Trait> traits = []>
31 : VerifDialectOp<mnemonic, traits> {}
33class ConditionOp<string mnemonic, list<Trait> traits = []>
34 : VerifDialectOp<mnemonic, traits#[ConditionOpInterface]> {
36 let arguments = (ins I1:$condition);
39 let assemblyFormat = [{ $condition attr-dict }];
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
47 effects.emplace_back(::mlir::MemoryEffects::Write::get());
52class ContractConditionOp<string mnemonic, bit inlinable = false,
53 list<Trait> traits = []>
54 : ConditionOp<mnemonic,
56 [HasAncestorOf<["::llzk::verif::ContractOp",
57 "::llzk::function::FuncDefOp"]>],
58 [HasAncestor<"::llzk::verif::ContractOp">])>;
60//===------------------------------------------------------------------===//
61// Struct verification condition operations
62//===------------------------------------------------------------------===//
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;
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;
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 }];
82//===------------------------------------------------------------------===//
83// Determinism operations
84//===------------------------------------------------------------------===//
86def ProveDetOp : VerifDialectOp<"det.prove", [Verification<>]> {
87 let summary = "proof obligation of determinism";
89 A proof obligation that the operand is deterministic, for a concrete definition
90 of determinism determined by the backend.
92 Returns a boolean that indicates if the operand was proven deterministic or not.
94 This operation has the `Verification` trait and can be used inside `function.def`
95 ops that have the `allow_verif` attribute.
97 let arguments = (ins EmitEqType:$condition);
98 let results = (outs I1:$result);
100 let assemblyFormat = "$condition attr-dict `:` type($condition)";
103def AssumeDetOp : VerifDialectOp<"det.assume", [Verification<>]> {
104 let summary = "hint of determinisms";
106 Hints to the proving backend that the operand is deterministic, for a concrete definition
107 of determinism determined by the backend.
109 This operation has the `Verification` trait and can be used inside `function.def`
110 ops that have the `allow_verif` attribute.
112 let arguments = (ins EmitEqType:$hint);
113 let results = (outs);
115 let assemblyFormat = "$hint attr-dict `:` type($hint)";
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
123 effects.emplace_back(::mlir::MemoryEffects::Write::get());
128//===------------------------------------------------------------------===//
129// Precondition operations
130//===------------------------------------------------------------------===//
132class RequireOpBase<string mnemonic, list<Trait> traits = []>
133 : ContractConditionOp<"require_"#mnemonic, false,
134 traits#[PreconditionOpInterface]> {
136 Encodes a precondition in the `@}]#mnemonic#[{` function.
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.
145def RequireComputeOp : RequireOpBase<"compute"> {
146 let summary = "witness computation precondition";
149def RequireConstrainOp : RequireOpBase<"constrain"> {
150 let summary = "constraint generation precondition";
153//===------------------------------------------------------------------===//
154// Postcondition operations
155//===------------------------------------------------------------------===//
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>], [])> {
166 Encodes a postcondition in the `@}]#mnemonic#[{` function.
169 This operation has the `Verification` trait and can be used inside `function.def`
170 ops that have the `allow_verif_ops` attribute.
175def EnsureComputeOp : EnsureOpBase<"compute"> {
176 let summary = "witness computation postcondition";
183 /*verificationExtraTraits=*/["::llzk::function::ConstraintGen"]> {
184 let summary = "constraint generation postcondition";
187//===------------------------------------------------------------------===//
189//===------------------------------------------------------------------===//
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";
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.
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.
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.
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.
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.
228 function.def @compute(%in : !felt.type) -> !struct.type<@Bar> { ... }
229 function.def @constrain(%self : !struct.type<@Bar>, %in : !felt.type) { ... }
232 verif.contract @FooContract for @Bar (%self : !struct.type<@Bar>, %in : !felt.type) {
236 function.def @free_func (%a : !felt.type) -> !felt.type { ... }
238 verif.contract @BarContract for @free_func (%a : !felt.type, %ret_value : !felt.type) {
245 let arguments = (ins SymbolNameAttr:$sym_name, SymbolRefAttr:$target,
246 TypeAttrOf<FunctionType>:$function_type,
247 OptionalAttr<DictArrayAttr>:$arg_attrs);
249 let regions = (region AnyRegion:$body);
251 let hasCustomAssemblyFormat = 1;
253 let hasRegionVerifier = 1;
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),
263 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
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),
271 $_state.getOrAddProperties<Properties>().sym_name = sym_name;
272 $_state.getOrAddProperties<Properties>().target = target;
273 $_state.getOrAddProperties<Properties>().function_type = function_type;
275 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
278 $_builder, $_state, ::llvm::cast<::mlir::FunctionType>(function_type.getValue())
280 assert(resultTypes.size() == 0u && "mismatched number of results");
281 $_state.addTypes(resultTypes);
283 OpBuilder<(ins "::llvm::StringRef":$sym_name,
284 "::mlir::SymbolRefAttr":$target,
285 "::mlir::FunctionType":$function_type,
286 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
288 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
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),
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);
300 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
302 initializeEmptyBody($_builder, $_state, function_type);
303 assert(resultTypes.size() == 0u && "mismatched number of results");
304 $_state.addTypes(resultTypes);
306 OpBuilder<(ins "::llvm::StringRef":$name,
307 "llvm::StringRef":$target)>,
308 OpBuilder<(ins "::llvm::StringRef":$name,
309 "::mlir::SymbolRefAttr":$target)>];
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
319 ContractOp clone(::mlir::IRMapping &mapper);
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
326 void cloneInto(ContractOp dest, ::mlir::IRMapping &mapper);
328 /// Return `true` iff the argument at the given index has `pub` attribute.
329 bool hasArgPublicAttr(unsigned index);
331 /// Return `true` iff the argument at the given index has a `function.arg_name` attribute.
332 bool hasArgName(unsigned index);
334 /// Return the `function.arg_name` attribute for the argument at the given index.
335 ::std::optional<::mlir::StringAttr> getArgNameAttr(unsigned index);
337 /// Set the `function.arg_name` attribute for the argument at the given index.
338 void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr);
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);
343 /// Required by FunctionOpInterface.
344 /// Returns the region on the current operation that is callable.
345 ::mlir::Region *getCallableRegion() { return &getBody(); }
347 /// Required by FunctionOpInterface.
348 /// Returns the argument types of this contract.
349 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
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(); }
356 /// Required by SymbolOpInterface.
357 bool isDeclaration() { return false; }
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);
363 /// Return `true` iff the contract targets a struct type.
364 bool hasStructTarget() { return succeeded(getStructTarget()); }
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);
370 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget() {
371 ::mlir::SymbolTableCollection tables;
372 return getStructTarget(tables);
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();
379 /// Return `true` iff the contract targets a function.
380 bool hasFuncTarget() { return succeeded(getFuncTarget()); }
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);
386 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget() {
387 ::mlir::SymbolTableCollection tables;
388 return getFuncTarget(tables);
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);
395 ::mlir::FailureOr<SymbolLookupResult<::llzk::verif::ContractTargetOpInterface>> getTargetOp() {
396 ::mlir::SymbolTableCollection tables;
397 return getTargetOp(tables);
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
410//===------------------------------------------------------------------===//
412//===------------------------------------------------------------------===//
415 : VerifDialectOp<"contract_end", [Pure, Terminator,
416 HasParent<"::llzk::verif::ContractOp">]> {
417 let summary = "terminates a verif.contract body";
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.
423 A terminator is expected by certain MLIR utilities (e.g., the data-flow analysis
424 framework), which is why this op is added.
427 let assemblyFormat = "attr-dict";
430//===------------------------------------------------------------------===//
432//===------------------------------------------------------------------===//
436 "include", [MemRefsNormalizable, AttrSizedOperandSegments,
437 VerifySizesForMultiAffineOps<1>,
438 DeclareOpInterfaceMethods<CallOpInterface>,
439 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
440 HasAncestor<"::llzk::verif::ContractOp">]> {
441 let summary = "contract inclusion operation";
443 Invokes another specification contract from another contract, effectively including the
444 specifications from another specification into the current contract.
447 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
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);
467 let assemblyFormat = [{
469 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
470 `` `(` $argOperands `)`
471 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
472 `:` functional-type($argOperands, results)
473 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
476 // Define builders manually so inference of operand layout attributes is not
478 let skipDefaultBuilders = 1;
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),
495 build($_builder, $_state, callee, mapOperands,
496 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
497 argOperands, templateParams);
499 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
500 CArg<"::mlir::ValueRange", "{}">:$argOperands,
501 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
502 "{}">:$templateParams),
504 build($_builder, $_state,
505 callee.getFullyQualifiedName(false),
506 argOperands, templateParams);
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),
515 build($_builder, $_state,
516 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
517 argOperands, templateParams);
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),
526 build($_builder, $_state, callee, mapOperands,
527 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
528 argOperands, templateParams);
531 let extraClassDeclaration = [{
532 /// Required by CallOpInterface
533 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
535 /// Required by CallOpInterface
536 ::mlir::Operation *resolveCallable();
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();
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);
548 /// Return `true` iff the contract targets a struct type.
549 bool contractTargetsStruct();
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();
555 /// Resolve and return the target Contract for this CallOp.
556 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::verif::ContractOp>>
557 getCalleeTarget(::mlir::SymbolTableCollection &tables);
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);
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
569 /// Check type compatibility of each template parameter value provided in this `CallOp` against
570 /// the declared type on each `TemplateParamOp` (if any).
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
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.
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
594//===------------------------------------------------------------------===//
596//===------------------------------------------------------------------===//
599 : VerifDialectOp<"invariant", [HasAncestor<"::llzk::verif::ContractOp">,
600 NoTerminator, SingleBlock]> {
602 let summary = "loop invariant definition operation";
604 Defines an invariant for a loop inside the target of a contract.
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`.
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.
617 module attributes {llzk.lang} {
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
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>
630 function.def @constrain(%self: !struct.type<@Top>) {
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
645 let arguments = (ins StrAttr:$loop_name, TypeArrayAttr:$loop_arg_types);
647 let regions = (region SizedRegion<1>:$region);
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)>];
654 let hasCustomAssemblyFormat = 1;
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();
665//===------------------------------------------------------------------===//
666// Invariant inner ops
667//===------------------------------------------------------------------===//
669class InvariantInnerOp<string mnemonic, list<Trait> traits = []>
672 traits#[HasAncestor<"::llzk::verif::InvariantOp">,
673 DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
675 let summary = mnemonic#" value operation";
677 Indicates that a value }]#mnemonic#[{ on each iteration of the loop.
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.
683 let arguments = (ins LLZK_FeltType:$value);
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
691 effects.emplace_back(::mlir::MemoryEffects::Write::get());
695 let assemblyFormat = "$value attr-dict";
698def IncreasesOp : InvariantInnerOp<"increases"> {}
700def DecreasesOp : InvariantInnerOp<"decreases"> {}
702def StepOp : VerifDialectOp<
703 "step", [HasAncestor<"::llzk::verif::InvariantOp">,
704 DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
705 NoRegionArguments, SingleBlock]> {
706 let summary = "step predicate operation";
708 Defines a predicate that must be satisfied between iterations of the loop.
710 The predicate can access the value in the previous iteration by using the `verif.old` operation.
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.
716 let regions = (region SizedRegion<1>:$region);
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
724 effects.emplace_back(::mlir::MemoryEffects::Write::get());
728 let assemblyFormat = "$region attr-dict";
732 : VerifDialectOp<"step.yield", [HasParent<"StepOp">, Terminator]> {
733 let summary = "step yield operation";
735 Terminator operation for `verif.step` blocks. Takes the boolean value carrying the predicate's result.
738 let arguments = (ins I1:$value);
740 let assemblyFormat = "$value attr-dict";
743def OldOp : VerifDialectOp<"old", [Pure, HasAncestor<"::llzk::verif::StepOp">,
744 AllTypesMatch<["value", "result"]>]> {
745 let summary = "old operation";
747 This operation allows accessing the value of an expression in the previous iteration of the loop.
749 In can only be used within the body of a `verif.step` operation.
752 let arguments = (ins AnyLLZKType:$value);
753 let results = (outs AnyLLZKType:$result);
755 let assemblyFormat = "$value `:` type($result) attr-dict";
758#endif // LLZK_VERIF_OPS