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 "mlir/Interfaces/CallInterfaces.td"
16include "mlir/Interfaces/FunctionInterfaces.td"
17include "llzk/Dialect/Shared/OpTraits.td"
18include "llzk/Dialect/Shared/Types.td"
20include "mlir/IR/OpAsmInterface.td"
21include "mlir/IR/OpBase.td"
22include "mlir/IR/SymbolInterfaces.td"
24class VerifDialectOp<string mnemonic, list<Trait> traits = []>
25 : Op<VerifDialect, mnemonic, traits>;
27class ConditionOp<string mnemonic, list<Trait> traits = []>
28 : VerifDialectOp<mnemonic,
29 traits#[ConditionOpInterface,
30 HasAncestor<"::llzk::verif::ContractOp">]> {
32 let arguments = (ins I1:$condition);
35 let assemblyFormat = [{ $condition attr-dict }];
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
43 effects.emplace_back(::mlir::MemoryEffects::Write::get());
48//===------------------------------------------------------------------===//
49// Precondition operations
50//===------------------------------------------------------------------===//
52class RequireOpBase<string mnemonic, list<Trait> traits = []>
53 : ConditionOp<mnemonic, traits#[PreconditionOpInterface]> {
55 Base of an operation that encodes a precondition.
58 - May not depend on values transitively derived from struct member reads
59 or from function return values.
60 - May not be present on the `llzk.main` entry struct.
64def RequireComputeOp : RequireOpBase<"require_compute"> {
65 let summary = "witness computation precondition";
68def RequireConstrainOp : RequireOpBase<"require_constrain"> {
69 let summary = "constraint generation precondition";
72//===------------------------------------------------------------------===//
73// Postcondition operations
74//===------------------------------------------------------------------===//
76class EnsureOpBase<string mnemonic, list<Trait> traits = []>
77 : ConditionOp<mnemonic, traits#[PostconditionOpInterface]>;
79def EnsureComputeOp : EnsureOpBase<"ensure_compute"> {
80 let summary = "witness computation postcondition";
83def EnsureConstrainOp : EnsureOpBase<"ensure_constrain"> {
84 let summary = "constraint generation postcondition";
87//===------------------------------------------------------------------===//
89//===------------------------------------------------------------------===//
94 [ParentOneOf<["::mlir::ModuleOp", "::llzk::polymorphic::TemplateOp"]>,
95 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
96 AutomaticAllocationScope, FunctionOpInterface,
97 SingleBlockImplicitTerminator<"::llzk::verif::ContractEndOp">,
99 let summary = "defines a specification contract for a given symbol";
101 A specification contract for a given function or struct in the circuit.
102 Contracts are "function-like", as they have arguments matching their targets,
103 and are "called" by the `verif.include` operation so that contracts can require
104 other contracts to hold on subcomponents, etc.
106 Contract argument rules:
107 - For function targets, the contract accepts arguments matching the function's arguments.
108 - For struct targets, the contract accepts arguments matching the argument list of the
109 struct's `@constrain` function.
111 Contracts do not return values. For function targets with return values, the
112 return values are appended to the end of the contract's argument list.
114 Contracts may not directly target struct functions; they must target the structs themselves.
115 Contracts targeting the module's `llzk.main` struct may not contain direct
116 `verif.require_compute` or `verif.require_constrain` operations in their body.
118 For templated targets (i.e., functions or structs within a `poly.template`),
119 the contract body may reference the target template's parameters and
120 expressions even if the contract is physically nested elsewhere.
127 function.def @compute(%in : !felt.type) -> !struct.type<@Bar> { ... }
128 function.def @constrain(%self : !struct.type<@Bar>, %in : !felt.type) { ... }
131 verif.contract @FooContract for @Bar (%self : !struct.type<@Bar>, %in : !felt.type) {
135 function.def @free_func (%a : !felt.type) -> !felt.type { ... }
137 verif.contract @BarContract for @free_func (%a : !felt.type, %ret_value : !felt.type) {
144 let arguments = (ins SymbolNameAttr:$sym_name, SymbolRefAttr:$target,
145 TypeAttrOf<FunctionType>:$function_type,
146 OptionalAttr<DictArrayAttr>:$arg_attrs);
148 let regions = (region AnyRegion:$body);
150 let hasCustomAssemblyFormat = 1;
152 let hasRegionVerifier = 1;
154 // Define builders manually so builder-created contracts always contain a
155 // complete single-block body with the implicit `verif.contract_end`.
156 let skipDefaultBuilders = 1;
157 let builders = [OpBuilder<(ins "::mlir::StringAttr":$sym_name,
158 "::mlir::SymbolRefAttr":$target,
159 "::mlir::TypeAttr":$function_type,
160 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
162 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
164 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
165 "::mlir::StringAttr":$sym_name,
166 "::mlir::SymbolRefAttr":$target,
167 "::mlir::TypeAttr":$function_type,
168 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
170 $_state.getOrAddProperties<Properties>().sym_name = sym_name;
171 $_state.getOrAddProperties<Properties>().target = target;
172 $_state.getOrAddProperties<Properties>().function_type = function_type;
174 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
177 $_builder, $_state, ::mlir::cast<::mlir::FunctionType>(function_type.getValue())
179 assert(resultTypes.size() == 0u && "mismatched number of results");
180 $_state.addTypes(resultTypes);
182 OpBuilder<(ins "::llvm::StringRef":$sym_name,
183 "::mlir::SymbolRefAttr":$target,
184 "::mlir::FunctionType":$function_type,
185 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
187 build($_builder, $_state, ::mlir::TypeRange {}, sym_name, target, function_type, arg_attrs);
189 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
190 "::llvm::StringRef":$sym_name,
191 "::mlir::SymbolRefAttr":$target,
192 "::mlir::FunctionType":$function_type,
193 CArg<"::mlir::ArrayAttr", "{}">:$arg_attrs),
195 $_state.getOrAddProperties<Properties>().sym_name = $_builder.getStringAttr(sym_name);
196 $_state.getOrAddProperties<Properties>().target = target;
197 $_state.getOrAddProperties<Properties>().function_type = ::mlir::TypeAttr::get(function_type);
199 $_state.getOrAddProperties<Properties>().arg_attrs = arg_attrs;
201 initializeEmptyBody($_builder, $_state, function_type);
202 assert(resultTypes.size() == 0u && "mismatched number of results");
203 $_state.addTypes(resultTypes);
205 OpBuilder<(ins "::llvm::StringRef":$name,
206 "llvm::StringRef":$target)>,
207 OpBuilder<(ins "::llvm::StringRef":$name,
208 "::mlir::SymbolRefAttr":$target)>];
210 let extraClassDeclaration = [{
211 /// Create a deep copy of this contract and all of its blocks, remapping any
212 /// operands that use values outside of the contract using the map that is
213 /// provided (leaving them alone if no entry is present). If the mapper
214 /// contains entries for contract arguments, these arguments are not
215 /// included in the new contract. Replaces references to cloned sub-values
216 /// with the corresponding value that is copied, and adds those mappings to
218 ContractOp clone(::mlir::IRMapping &mapper);
221 /// Clone the internal blocks and attributes from this contract into dest.
222 /// Any cloned blocks are appended to the back of dest. This contract
223 /// asserts that the attributes of the current contract and dest are
225 void cloneInto(ContractOp dest, ::mlir::IRMapping &mapper);
227 /// Return `true` iff the argument at the given index has `pub` attribute.
228 bool hasArgPublicAttr(unsigned index);
230 /// Return `true` iff the argument at the given index has a `function.arg_name` attribute.
231 bool hasArgName(unsigned index);
233 /// Return the `function.arg_name` attribute for the argument at the given index.
234 ::std::optional<::mlir::StringAttr> getArgNameAttr(unsigned index);
236 /// Set the `function.arg_name` attribute for the argument at the given index.
237 void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr);
239 /// Set the `function.arg_name` attribute for the argument at the given index from a string.
240 void setArgName(unsigned index, ::llvm::StringRef name);
242 /// Required by FunctionOpInterface.
243 /// Returns the region on the current operation that is callable.
244 ::mlir::Region *getCallableRegion() { return &getBody(); }
246 /// Required by FunctionOpInterface.
247 /// Returns the argument types of this contract.
248 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
250 /// Required by FunctionOpInterface.
251 /// Returns the result types of this contract. Since contracts don't have
252 /// return values, the returned ArrayRef will always be empty.
253 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
255 /// Required by SymbolOpInterface.
256 bool isDeclaration() { return false; }
258 /// Return the full name for this contract from the root module, including
259 /// all surrounding symbol table names (i.e., modules and structs).
260 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
262 /// Return `true` iff the contract targets a struct type.
263 bool hasStructTarget() { return succeeded(getStructTarget()); }
265 /// Return the StructDefOp that this contract targets, or failure if it does not
266 /// target a struct or the struct is not found.
267 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget(::mlir::SymbolTableCollection &tables);
269 ::mlir::FailureOr<SymbolLookupResult<component::StructDefOp>> getStructTarget() {
270 ::mlir::SymbolTableCollection tables;
271 return getStructTarget(tables);
274 /// Return the "self" value (i.e. the first parameter) from the contract, or
275 /// failure if the contract does not target a struct.
276 ::mlir::FailureOr<::mlir::Value> getSelfValue();
278 /// Return `true` iff the contract targets a function.
279 bool hasFuncTarget() { return succeeded(getFuncTarget()); }
281 /// Return the FuncDefOp that this contract targets, or failure if it does not
282 /// target a function or the function is not found.
283 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget(::mlir::SymbolTableCollection &tables);
285 ::mlir::FailureOr<SymbolLookupResult<function::FuncDefOp>> getFuncTarget() {
286 ::mlir::SymbolTableCollection tables;
287 return getFuncTarget(tables);
291 /// Populate a builder-created contract body with one entry block matching
292 /// the function signature and insert the implicit `verif.contract_end`.
293 static void initializeEmptyBody(
294 ::mlir::OpBuilder &builder, ::mlir::OperationState &state,
295 ::mlir::FunctionType functionType
300//===------------------------------------------------------------------===//
302//===------------------------------------------------------------------===//
305 : VerifDialectOp<"contract_end", [Pure, Terminator,
306 HasParent<"::llzk::verif::ContractOp">]> {
307 let summary = "terminates a verif.contract body";
309 Implicit terminator for `verif.contract` regions. This operation is inserted
310 automatically by the parser and omitted by the custom printer so contract
311 syntax remains terminator-free.
313 A terminator is expected by certain MLIR utilities (e.g., the data-flow analysis
314 framework), which is why this op is added.
317 let assemblyFormat = "attr-dict";
320//===------------------------------------------------------------------===//
322//===------------------------------------------------------------------===//
326 "include", [MemRefsNormalizable, AttrSizedOperandSegments,
327 VerifySizesForMultiAffineOps<1>,
328 DeclareOpInterfaceMethods<CallOpInterface>,
329 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
330 HasAncestor<"::llzk::verif::ContractOp">]> {
331 let summary = "contract inclusion operation";
333 Invokes another specification contract from another contract, effectively including the
334 specifications from another specification into the current contract.
337 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
339 // Call target contract reference.
340 SymbolRefAttr:$callee,
341 // List of arguments to call the target contract.
342 Variadic<AnyLLZKType>:$argOperands,
343 // List of parameters to instantiate all `poly.param` symbols when the
344 // callee is a contract inside a `poly.template` region.
345 OptionalAttr<ArrayAttr>:$templateParams,
346 // List of AffineMap operand groups where each group provides the
347 // arguments to instantiate the next (left-to-right) AffineMap used as a
348 // struct parameter in the result StructType.
349 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
350 // Within each group in '$mapOperands', denotes the number of values that
351 // are AffineMap "dimensional" arguments with the remaining values being
352 // AffineMap "symbolic" arguments.
353 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
354 // Denotes the size of each variadic group in '$mapOperands'.
355 DenseI32ArrayAttr:$mapOpGroupSizes);
357 let assemblyFormat = [{
359 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
360 `` `(` $argOperands `)`
361 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
362 `:` functional-type($argOperands, results)
363 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
366 // Define builders manually so inference of operand layout attributes is not
368 let skipDefaultBuilders = 1;
370 [OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
371 CArg<"::mlir::ValueRange", "{}">:$argOperands,
372 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
373 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
374 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
375 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
376 CArg<"::mlir::ValueRange", "{}">:$argOperands,
377 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
378 OpBuilder<(ins "::mlir::SymbolRefAttr":$callee,
379 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
380 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
381 CArg<"::mlir::ValueRange", "{}">:$argOperands,
382 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
383 "{}">:$templateParams),
385 build($_builder, $_state, callee, mapOperands,
386 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
387 argOperands, templateParams);
389 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
390 CArg<"::mlir::ValueRange", "{}">:$argOperands,
391 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
392 "{}">:$templateParams),
394 build($_builder, $_state,
395 callee.getFullyQualifiedName(false),
396 argOperands, templateParams);
398 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
399 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
400 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
401 CArg<"::mlir::ValueRange", "{}">:$argOperands,
402 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
403 "{}">:$templateParams),
405 build($_builder, $_state,
406 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
407 argOperands, templateParams);
409 OpBuilder<(ins "::llzk::verif::ContractOp":$callee,
410 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
411 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
412 CArg<"::mlir::ValueRange", "{}">:$argOperands,
413 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
414 "{}">:$templateParams),
416 build($_builder, $_state, callee, mapOperands,
417 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
418 argOperands, templateParams);
421 let extraClassDeclaration = [{
422 /// Required by CallOpInterface
423 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
425 /// Required by CallOpInterface
426 ::mlir::Operation *resolveCallable();
428 /// Return the FunctionType inferred from the arg operands of this CallOp.
429 /// This is not necessarily the same as the callee's FunctionType but should unify with it
430 /// or else IR verification will fail.
431 ::mlir::FunctionType getTypeSignature();
433 /// Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and
434 /// the given FunctionType (as RHS). If successful, return a UnificationMap containing the
435 /// unifications that were made. Otherwise, return failure.
436 ::mlir::FailureOr<UnificationMap> unifyTypeSignature(::mlir::FunctionType other);
438 /// Return `true` iff the contract targets a struct type.
439 bool contractTargetsStruct();
441 /// Return the "self" value (i.e. the first parameter) from the callee contract,
442 /// assuming the target of the contract is a struct target.
443 ::mlir::Value getSelfValue();
445 /// Resolve and return the target Contract for this CallOp.
446 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::verif::ContractOp>>
447 getCalleeTarget(::mlir::SymbolTableCollection &tables);
449 /// Allocate consecutive storage of the ValueRange instances in the parameter
450 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
451 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
453 /// Check type compatibility of the given template parameter value from this `CallOp` against
454 /// the declared type on the given `TemplateParamOp` (if any).
455 ::mlir::LogicalResult verifyTemplateParamCompatibility(
456 ::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam
459 /// Check type compatibility of each template parameter value provided in this `CallOp` against
460 /// the declared type on each `TemplateParamOp` (if any).
462 /// Pre-condition assertions:
463 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
464 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
465 ::mlir::LogicalResult verifyTemplateParamCompatibility(
466 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs
469 /// Verify that each template parameter value provided in this `CallOp` is consistent with
470 /// the value inferred for the target `TemplateParamOp` in the given `UnificationMap`. The
471 /// `UnificationMap` is expected to contain the unification results of this `CallOp` against
472 /// the target function type signature.
474 /// Pre-condition assertions:
475 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
476 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
477 ::mlir::LogicalResult verifyTemplateParamsMatchInferred(
478 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs,
479 const UnificationMap &unifications
484#endif // LLZK_VERIF_OPS