LLZK 2.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 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8// Adapted from mlir/include/mlir/Dialect/Func/IR/FuncOps.td
9// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
10// See https://llvm.org/LICENSE.txt for license information.
11// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
12//
13//===----------------------------------------------------------------------===//
14
15#ifndef LLZK_FUNC_OPS
16#define LLZK_FUNC_OPS
17
18include "llzk/Dialect/Function/IR/Dialect.td"
19include "llzk/Dialect/Shared/OpTraits.td"
20include "llzk/Dialect/Shared/Types.td"
21
22include "mlir/IR/OpAsmInterface.td"
23include "mlir/IR/SymbolInterfaces.td"
24include "mlir/Interfaces/CallInterfaces.td"
25include "mlir/Interfaces/ControlFlowInterfaces.td"
26include "mlir/Interfaces/FunctionInterfaces.td"
27include "mlir/Interfaces/InferTypeOpInterface.td"
28include "mlir/Interfaces/SideEffectInterfaces.td"
29
30class FunctionDialectOp<string mnemonic, list<Trait> traits = []>
31 : Op<FunctionDialect, mnemonic, traits>;
32
33//===----------------------------------------------------------------------===//
34// FuncDefOp
35//===----------------------------------------------------------------------===//
36
37def FuncDefOp
38 : FunctionDialectOp<
39 "def",
40 [ParentOneOf<["::mlir::ModuleOp", "::llzk::component::StructDefOp",
41 "::llzk::polymorphic::TemplateOp"]>,
42 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
43 AutomaticAllocationScope, FunctionOpInterface, IsolatedFromAbove]> {
44 // NOTE: Cannot have SymbolTable trait because that would cause global
45 // functions without a body to produce "Operations with a 'SymbolTable' must
46 // have exactly one block"
47 let summary = "An operation with a name containing a single `SSACFG` region";
48 let description = [{
49 Operations within the function cannot implicitly capture values defined
50 outside of the function, i.e., functions are `IsolatedFromAbove`. All
51 external references must use function arguments (which are passed by value)
52 or reference external members or globals by symbol name.
53
54 Functions appearing within a `struct.def` have specific semantics and must
55 be named `compute`, `constrain`, or `product`. Functions appearing at the
56 module level (i.e. not within a `struct.def`) have no name restrictions and
57 their body may be elided to denote an external function declaration.
58
59 Modules and `struct.def` ops are not allowed to be nested within functions.
60
61 Example:
62
63 ```llzk
64 // External function definitions.
65 function.def private @abort()
66 function.def private @scribble(!array.type<5 x !felt.type>, !struct.type<@Hello>) -> i1
67
68 // A function that returns its argument twice:
69 function.def @count(%x: !felt.type) -> (!felt.type, !felt.type) {
70 return %x, %x: !felt.type, !felt.type
71 }
72
73 // Function definition within a component
74 struct.def @NonZero {
75 function.def @compute(%a: !felt.type) { return }
76 function.def @constrain(%a: !felt.type) { return }
77 }
78 ```
79 }];
80
81 // Duplicated from the pre-defined `func` dialect. We don't store the
82 // visibility attribute but, since we use `function_interface_impl` for
83 // parsing/printing, there is still the requirement that global functions
84 // declared without a body must specify the `private` visibility.
85 // Additionally, the default parsing/printing functions allow attributes on
86 // the arguments, results, and function itself.
87 // ```llzk
88 // // Argument attribute
89 // function.def private @example_fn_arg(%x: i1 {llzk.pub})
90 //
91 // // Result attribute
92 // function.def @example_fn_result() -> (i1 {dialectName.attrName = 0 :
93 // i1})
94 //
95 // // Function attribute
96 // function.def @example_fn_attr() attributes {dialectName.attrName =
97 // false}
98 // ```
99 let arguments = (ins SymbolNameAttr:$sym_name,
100 TypeAttrOf<FunctionType>:$function_type,
101 OptionalAttr<DictArrayAttr>:$arg_attrs,
102 OptionalAttr<DictArrayAttr>:$res_attrs);
103 let regions = (region AnyRegion:$body);
104
105 let builders = [OpBuilder<(ins "::llvm::StringRef":$name,
106 "::mlir::FunctionType":$type,
107 CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>", "{}">:$attrs,
108 CArg<"::llvm::ArrayRef<::mlir::DictionaryAttr>", "{}">:$argAttrs)>];
109
110 let extraClassDeclaration = [{
111 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
112 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs = {});
113 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
114 ::mlir::Operation::dialect_attr_range attrs);
115 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
116 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs,
117 ::llvm::ArrayRef<::mlir::DictionaryAttr> argAttrs);
118
119 /// Create a deep copy of this function and all of its blocks, remapping any
120 /// operands that use values outside of the function using the map that is
121 /// provided (leaving them alone if no entry is present). If the mapper
122 /// contains entries for function arguments, these arguments are not
123 /// included in the new function. Replaces references to cloned sub-values
124 /// with the corresponding value that is copied, and adds those mappings to
125 /// the mapper.
126 FuncDefOp clone(::mlir::IRMapping &mapper);
127 FuncDefOp clone();
128
129 /// Clone the internal blocks and attributes from this function into dest.
130 /// Any cloned blocks are appended to the back of dest. This function
131 /// asserts that the attributes of the current function and dest are
132 /// compatible.
133 void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper);
134
135 /// Return `true` iff the function def has the `allow_constraint` attribute.
136 inline bool hasAllowConstraintAttr() {
137 return getOperation()->hasAttr(llzk::function::AllowConstraintAttr::name);
138 }
139
140 /// Add (resp. remove) the `allow_constraint` attribute to (resp. from) the function def.
141 void setAllowConstraintAttr(bool newValue = true);
142
143 /// Return `true` iff the function def has the `allow_witness` attribute.
144 inline bool hasAllowWitnessAttr() {
145 return getOperation()->hasAttr(llzk::function::AllowWitnessAttr::name);
146 }
147
148 /// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
149 void setAllowWitnessAttr(bool newValue = true);
150
151 /// Return `true` iff the function def has the `allow_non_native_field_ops` attribute.
152 inline bool hasAllowNonNativeFieldOpsAttr() {
153 return getOperation()->hasAttr(llzk::function::AllowNonNativeFieldOpsAttr::name);
154 }
155
156 /// Add (resp. remove) the `allow_non_native_field_ops` attribute to (resp. from) the function def.
157 void setAllowNonNativeFieldOpsAttr(bool newValue = true);
158
159 /// Return `true` iff the argument at the given index has `pub` attribute.
160 bool hasArgPublicAttr(unsigned index);
161
162 /// Required by FunctionOpInterface.
163 /// Returns the region on the current operation that is callable. This may
164 /// return null in the case of an external callable object, e.g. an external
165 /// function.
166 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
167
168 /// Required by FunctionOpInterface.
169 /// Returns the argument types of this function.
170 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
171
172 /// Required by FunctionOpInterface.
173 /// Returns the result types of this function.
174 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
175
176 /// Required by SymbolOpInterface.
177 bool isDeclaration() { return isExternal(); }
178
179 /// Return the full name for this function from the root module, including
180 /// all surrounding symbol table names (i.e., modules and structs).
181 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
182
183 /// Return `true` iff the function name is `FUNC_NAME_COMPUTE` (if needed, a check
184 /// that this FuncDefOp is located within a StructDefOp must be done separately).
185 inline bool nameIsCompute() { return FUNC_NAME_COMPUTE == getSymName(); }
186
187 /// Return `true` iff the function name is `FUNC_NAME_CONSTRAIN` (if needed, a
188 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
189 inline bool nameIsConstrain() { return FUNC_NAME_CONSTRAIN == getSymName(); }
190
191 /// Return `true` iff the function name is `FUNC_NAME_PRODUCT` (if needed, a
192 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
193 inline bool nameIsProduct() { return FUNC_NAME_PRODUCT == getSymName(); }
194
195 /// Return `true` iff the function is within a StructDefOp
196 inline bool isInStruct() { return ::llzk::component::isInStruct(*this); }
197
198 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
199 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
200
201 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
202 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
203
204 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_PRODUCT`.
205 inline bool isStructProduct() { return isInStruct() && nameIsProduct(); }
206
207 /// Return the "self" value (i.e. the return value) from the function (which must be
208 /// named `FUNC_NAME_COMPUTE`).
209 ::mlir::Value getSelfValueFromCompute();
210
211 /// Return the "self" value (i.e. the first parameter) from the function (which must be
212 /// named `FUNC_NAME_CONSTRAIN`).
213 ::mlir::Value getSelfValueFromConstrain();
214
215 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
216 ::llzk::component::StructType getSingleResultTypeOfCompute();
217 }];
218
219 let hasCustomAssemblyFormat = 1;
220 let hasVerifier = 1;
221}
222
223//===----------------------------------------------------------------------===//
224// ReturnOp
225//===----------------------------------------------------------------------===//
226
227def ReturnOp
228 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
229 Pure, MemRefsNormalizable, ReturnLike,
230 Terminator]> {
231 let summary = "Function return operation";
232 let description = [{
233 The `function.return` operation represents a return operation within a function.
234 The operation takes variable number of operands and produces no results.
235 The operand number and types must match the signature of the function
236 that contains the operation.
237
238 Example:
239
240 ```llzk
241 function.def @foo() : (!felt.type, index) {
242 ...
243 return %0, %1 : !felt.type, index
244 }
245 ```
246 }];
247
248 let arguments = (ins Variadic<AnyLLZKType>:$operands);
249
250 let builders = [OpBuilder<(ins), [{
251 build($_builder, $_state, std::nullopt);
252 }]>];
253
254 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
255 let hasVerifier = 1;
256}
257
258//===----------------------------------------------------------------------===//
259// CallOp
260//===----------------------------------------------------------------------===//
261
262def CallOp : FunctionDialectOp<
263 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
264 VerifySizesForMultiAffineOps<1>,
265 DeclareOpInterfaceMethods<CallOpInterface>,
266 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
267 let summary = "call operation";
268 let description = [{
269 The `function.call` operation represents a call to another function. The operands
270 and result types of the call must match the specified function type. The
271 callee is encoded as a symbol reference attribute named "callee" which must
272 be the full path to the target function from the root module (i.e., the module
273 containing the [llzk::LANG_ATTR_NAME] attribute).
274
275 Example:
276 ```llzk
277 // Call a global function defined in the root module.
278 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
279 %1, %2 = function.call @split(%x) : (index) -> (index, index)
280
281 // Call a function within a component
282 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
283 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
284 ```
285
286 When the return StructType of a `compute()` function uses AffineMapAttr to
287 express struct parameter(s) that depend on a loop variable, the optional
288 instantiation parameter list of this operation must be used to instatiate
289 all AffineMap used as parameters to the StructType.
290
291 Examples:
292 ```llzk
293 #M = affine_map<(i)[] -> (5*i+1)>
294 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
295 ```
296 }];
297
298 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
299 let arguments = (ins
300 // Call target function reference.
301 SymbolRefAttr:$callee,
302 // List of arguments to call the target function.
303 Variadic<AnyLLZKType>:$argOperands,
304 // List of parameters to instantiate all `poly.param` symbols when the
305 // callee is a free function inside a `poly.template` region. If all
306 // `poly.param` symbols are used within the function signature, this can
307 // be elided. Otherwise, it is required to instantiate the function.
308 OptionalAttr<ArrayAttr>:$templateParams,
309 // List of AffineMap operand groups where each group provides the
310 // arguments to instantiate the next (left-to-right) AffineMap used as a
311 // struct parameter in the result StructType.
312 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
313 // Within each group in '$mapOperands', denotes the number of values that
314 // are AffineMap "dimensional" arguments with the remaining values being
315 // AffineMap "symbolic" arguments.
316 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
317 // Denotes the size of each variadic group in '$mapOperands'.
318 DenseI32ArrayAttr:$mapOpGroupSizes);
319 let results = (outs Variadic<AnyLLZKType>);
320
321 let assemblyFormat = [{
322 $callee
323 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
324 `` `(` $argOperands `)`
325 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
326 `:` functional-type($argOperands, results)
327 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
328 }];
329
330 let useCustomPropertiesEncoding = 1;
331
332 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
333 // call `verifyAffineMapInstantiations()`. However, in this op that check must
334 // happen within `verifySymbolUses()` instead because the target FuncDefOp
335 // must be resolved to determine if a target function named
336 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
337 // because the verification differs for those cases.
338
339 // Define builders manually so inference of operand layout attributes is not
340 // circumvented.
341 let skipDefaultBuilders = 1;
342 let builders =
343 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
344 "::mlir::SymbolRefAttr":$callee,
345 CArg<"::mlir::ValueRange", "{}">:$argOperands,
346 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
347 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
348 "::mlir::SymbolRefAttr":$callee,
349 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
350 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
351 CArg<"::mlir::ValueRange", "{}">:$argOperands,
352 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
353 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
354 "::mlir::SymbolRefAttr":$callee,
355 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
356 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
357 CArg<"::mlir::ValueRange", "{}">:$argOperands,
358 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
359 "{}">:$templateParams),
360 [{
361 build($_builder, $_state, resultTypes, callee, mapOperands,
362 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
363 argOperands, templateParams);
364 }]>,
365 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
366 CArg<"::mlir::ValueRange", "{}">:$argOperands,
367 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
368 "{}">:$templateParams),
369 [{
370 build($_builder, $_state, callee.getResultTypes(),
371 callee.getFullyQualifiedName(false),
372 argOperands, templateParams);
373 }]>,
374 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
375 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
376 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
377 CArg<"::mlir::ValueRange", "{}">:$argOperands,
378 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
379 "{}">:$templateParams),
380 [{
381 build($_builder, $_state, callee.getResultTypes(),
382 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
383 argOperands, templateParams);
384 }]>,
385 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
386 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
387 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
388 CArg<"::mlir::ValueRange", "{}">:$argOperands,
389 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
390 "{}">:$templateParams),
391 [{
392 build($_builder, $_state, callee, mapOperands,
393 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
394 argOperands, templateParams);
395 }]>];
396
397 let extraClassDeclaration = [{
398 /// Required by CallOpInterface
399 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
400
401 /// Required by CallOpInterface
402 ::mlir::Operation *resolveCallable();
403
404 /// Return the FunctionType inferred from the arg operands and result types of this CallOp.
405 /// This is not necessarily the same as the callee's FunctionType but should unify with it
406 /// or else IR verification will fail.
407 ::mlir::FunctionType getTypeSignature();
408
409 /// Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and
410 /// the given FunctionType (as RHS). If successful, return a UnificationMap containing the
411 /// unifications that were made. Otherwise, return failure.
412 ::mlir::FailureOr<UnificationMap> unifyTypeSignature(::mlir::FunctionType other);
413
414 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
415 /// does not check if the callee function is located within a StructDefOp).
416 inline bool calleeIsCompute() {
417 return FUNC_NAME_COMPUTE == getCallee().getLeafReference();
418 }
419
420 /// Return `true` iff the callee function can contain witness generation code
421 /// (this does not check if the callee function is located within a StructDefOp)
422 inline bool calleeContainsWitnessGen() {
423 return FUNC_NAME_COMPUTE == getCallee().getLeafReference() ||
424 FUNC_NAME_PRODUCT == getCallee().getLeafReference();
425 }
426
427 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
428 /// does not check if the callee function is located within a StructDefOp).
429 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
430
431 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
432 bool calleeIsStructCompute();
433
434 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
435 bool calleeIsStructConstrain();
436
437 /// Return the "self" value (i.e. the return value) from the callee function (which must be
438 /// named `FUNC_NAME_COMPUTE`).
439 ::mlir::Value getSelfValueFromCompute();
440
441 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
442 /// named `FUNC_NAME_CONSTRAIN`).
443 ::mlir::Value getSelfValueFromConstrain();
444
445 /// Resolve and return the target FuncDefOp for this CallOp.
446 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
447 getCalleeTarget(::mlir::SymbolTableCollection &tables);
448
449 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
450 ::llzk::component::StructType getSingleResultTypeOfCompute();
451
452 /// Assuming the callee contains witness generation code, return the single StructType result.
453 ::llzk::component::StructType getSingleResultTypeOfWitnessGen();
454
455 /// Allocate consecutive storage of the ValueRange instances in the parameter
456 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
457 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
458
459 /// Check type compatibility of the given template parameter value from this `CallOp` against
460 /// the declared type on the given `TemplateParamOp` (if any).
461 ::mlir::LogicalResult verifyTemplateParamCompatibility(
462 ::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam
463 );
464
465 /// Check type compatibility of each template parameter value provided in this `CallOp` against
466 /// the declared type on each `TemplateParamOp` (if any).
467 ///
468 /// Pre-condition assertions:
469 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
470 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
471 ::mlir::LogicalResult verifyTemplateParamCompatibility(
472 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs
473 );
474
475 /// Verify that each template parameter value provided in this `CallOp` is consistent with
476 /// the value inferred for the target `TemplateParamOp` in the given `UnificationMap`. The
477 /// `UnificationMap` is expected to contain the unification results of this `CallOp` against
478 /// the target function type signature.
479 ///
480 /// Pre-condition assertions:
481 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
482 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
483 ::mlir::LogicalResult verifyTemplateParamsMatchInferred(
484 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs,
485 const UnificationMap &unifications
486 );
487 }];
488}
489
490#endif // LLZK_FUNC_OPS