LLZK 0.1.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 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
42 AutomaticAllocationScope, FunctionOpInterface, IsolatedFromAbove]> {
43 // NOTE: Cannot have SymbolTable trait because that would cause global
44 // functions without a body to produce "Operations with a 'SymbolTable' must
45 // have exactly one block"
46 let summary = "An operation with a name containing a single `SSACFG` region";
47 let description = [{
48 Operations within the function cannot implicitly capture values defined
49 outside of the function, i.e., Functions are `IsolatedFromAbove`. All
50 external references must use function arguments or attributes that establish
51 a symbolic connection (e.g. symbols referenced by name via a string
52 attribute like SymbolRefAttr). An external function declaration (used when
53 referring to a function declared in some other module) has no body. While
54 the MLIR textual form provides a nice inline syntax for function arguments,
55 they are internally represented as “block arguments” to the first block in
56 the region.
57
58 Only dialect attribute names may be specified in the attribute dictionaries
59 for function arguments, results, or the function itself.
60
61 Modules and struct definitions are not allowed to be nested within functions.
62
63 Example:
64
65 ```llzk
66 // External function definitions.
67 function.def private @abort()
68 function.def private @scribble(!array.type<5 x !felt.type>, !struct.type<@Hello>) -> i1
69
70 // A function that returns its argument twice:
71 function.def @count(%x: !felt.type) -> (!felt.type, !felt.type) {
72 return %x, %x: !felt.type, !felt.type
73 }
74
75 // Function definition within a component
76 struct.def @NonZero {
77 function.def @compute(%a: !felt.type) { return }
78 function.def @constrain(%a: !felt.type) { return }
79 }
80 ```
81 }];
82
83 // Duplicated from the pre-defined `func` dialect. We don't store the
84 // visibility attribute but, since we use `function_interface_impl` for
85 // parsing/printing, there is still the requirement that global functions
86 // declared without a body must specify the `private` visibility.
87 // Additionally, the default parsing/printing functions allow attributes on
88 // the arguments, results, and function itself.
89 // ```llzk
90 // // Argument attribute
91 // function.def private @example_fn_arg(%x: i1 {llzk.pub})
92 //
93 // // Result attribute
94 // function.def @example_fn_result() -> (i1 {dialectName.attrName = 0 :
95 // i1})
96 //
97 // // Function attribute
98 // function.def @example_fn_attr() attributes {dialectName.attrName =
99 // false}
100 // ```
101 let arguments = (ins SymbolNameAttr:$sym_name,
102 TypeAttrOf<FunctionType>:$function_type,
103 OptionalAttr<DictArrayAttr>:$arg_attrs,
104 OptionalAttr<DictArrayAttr>:$res_attrs);
105 let regions = (region AnyRegion:$body);
106
107 let builders = [OpBuilder<(ins "::llvm::StringRef":$name,
108 "::mlir::FunctionType":$type,
109 CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>", "{}">:$attrs,
110 CArg<"::llvm::ArrayRef<::mlir::DictionaryAttr>", "{}">:$argAttrs)>];
111
112 let extraClassDeclaration = [{
113 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
114 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs = {});
115 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
116 ::mlir::Operation::dialect_attr_range attrs);
117 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
118 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs,
119 ::llvm::ArrayRef<::mlir::DictionaryAttr> argAttrs);
120
121 /// Create a deep copy of this function and all of its blocks, remapping any
122 /// operands that use values outside of the function using the map that is
123 /// provided (leaving them alone if no entry is present). If the mapper
124 /// contains entries for function arguments, these arguments are not
125 /// included in the new function. Replaces references to cloned sub-values
126 /// with the corresponding value that is copied, and adds those mappings to
127 /// the mapper.
128 FuncDefOp clone(::mlir::IRMapping &mapper);
129 FuncDefOp clone();
130
131 /// Clone the internal blocks and attributes from this function into dest.
132 /// Any cloned blocks are appended to the back of dest. This function
133 /// asserts that the attributes of the current function and dest are
134 /// compatible.
135 void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper);
136
137 /// Return `true` iff the function def has the `allow_constraint` attribute.
138 inline bool hasAllowConstraintAttr() {
139 return getOperation()->hasAttr(llzk::function::AllowConstraintAttr::name);
140 }
141
142 /// Add (resp. remove) the `allow_constraint` attribute to (resp. from) the function def.
143 void setAllowConstraintAttr(bool newValue = true);
144
145 /// Return `true` iff the function def has the `allow_witness` attribute.
146 inline bool hasAllowWitnessAttr() {
147 return getOperation()->hasAttr(llzk::function::AllowWitnessAttr::name);
148 }
149
150 /// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
151 void setAllowWitnessAttr(bool newValue = true);
152
153 /// Return `true` iff the function def has the `allow_non_native_field_ops` attribute.
154 inline bool hasAllowNonNativeFieldOpsAttr() {
155 return getOperation()->hasAttr(llzk::function::AllowNonNativeFieldOpsAttr::name);
156 }
157
158 /// Add (resp. remove) the `allow_non_native_field_ops` attribute to (resp. from) the function def.
159 void setAllowNonNativeFieldOpsAttr(bool newValue = true);
160
161 /// Return `true` iff the argument at the given index has `pub` attribute.
162 bool hasArgPublicAttr(unsigned index);
163
164 //===------------------------------------------------------------------===//
165 // FunctionOpInterface Methods
166 //===------------------------------------------------------------------===//
167
168 /// Returns the region on the current operation that is callable. This may
169 /// return null in the case of an external callable object, e.g. an external
170 /// function.
171 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
172
173 /// Returns the argument types of this function.
174 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
175
176 /// Returns the result types of this function.
177 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
178
179 //===------------------------------------------------------------------===//
180 // SymbolOpInterface Methods
181 //===------------------------------------------------------------------===//
182
183 bool isDeclaration() { return isExternal(); }
184
185 //===------------------------------------------------------------------===//
186 // Utility Methods
187 //===------------------------------------------------------------------===//
188
189 /// Return the full name for this function from the root module, including
190 /// all surrounding symbol table names (i.e., modules and structs).
191 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
192
193 /// Return `true` iff the function name is `FUNC_NAME_COMPUTE` (if needed, a check
194 /// that this FuncDefOp is located within a StructDefOp must be done separately).
195 inline bool nameIsCompute() { return FUNC_NAME_COMPUTE == getSymName(); }
196
197 /// Return `true` iff the function name is `FUNC_NAME_CONSTRAIN` (if needed, a
198 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
199 inline bool nameIsConstrain() { return FUNC_NAME_CONSTRAIN == getSymName(); }
200
201 /// Return `true` iff the function name is `FUNC_NAME_PRODUCT` (if needed, a
202 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
203 inline bool nameIsProduct() { return FUNC_NAME_PRODUCT == getSymName(); }
204
205 /// Return `true` iff the function is within a StructDefOp
206 inline bool isInStruct() { return ::llzk::component::isInStruct(*this); }
207
208 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
209 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
210
211 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
212 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
213
214 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_PRODUCT`.
215 inline bool isStructProduct() { return isInStruct() && nameIsProduct(); }
216
217 /// Return the "self" value (i.e. the return value) from the function (which must be
218 /// named `FUNC_NAME_COMPUTE`).
219 ::mlir::Value getSelfValueFromCompute();
220
221 /// Return the "self" value (i.e. the first parameter) from the function (which must be
222 /// named `FUNC_NAME_CONSTRAIN`).
223 ::mlir::Value getSelfValueFromConstrain();
224
225 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
226 ::llzk::component::StructType getSingleResultTypeOfCompute();
227 }];
228
229 let hasCustomAssemblyFormat = 1;
230 let hasVerifier = 1;
231}
232
233//===----------------------------------------------------------------------===//
234// ReturnOp
235//===----------------------------------------------------------------------===//
236
237def ReturnOp
238 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
239 Pure, MemRefsNormalizable, ReturnLike,
240 Terminator]> {
241 let summary = "Function return operation";
242 let description = [{
243 The `function.return` operation represents a return operation within a function.
244 The operation takes variable number of operands and produces no results.
245 The operand number and types must match the signature of the function
246 that contains the operation.
247
248 Example:
249
250 ```llzk
251 function.def @foo() : (!felt.type, index) {
252 ...
253 return %0, %1 : !felt.type, index
254 }
255 ```
256 }];
257
258 let arguments = (ins Variadic<AnyLLZKType>:$operands);
259
260 let builders = [OpBuilder<(ins), [{
261 build($_builder, $_state, std::nullopt);
262 }]>];
263
264 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
265 let hasVerifier = 1;
266}
267
268//===----------------------------------------------------------------------===//
269// CallOp
270//===----------------------------------------------------------------------===//
271
272def CallOp : FunctionDialectOp<
273 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
274 VerifySizesForMultiAffineOps<1>,
275 DeclareOpInterfaceMethods<CallOpInterface>,
276 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
277 let summary = "call operation";
278 let description = [{
279 The `function.call` operation represents a call to another function. The operands
280 and result types of the call must match the specified function type. The
281 callee is encoded as a symbol reference attribute named "callee" which must
282 be the full path to the target function from the root module (i.e., the module
283 containing the [llzk::LANG_ATTR_NAME] attribute).
284
285 Example:
286 ```llzk
287 // Call a global function defined in the root module.
288 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
289 %1, %2 = function.call @split(%x) : (index) -> (index, index)
290
291 // Call a function within a component
292 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
293 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
294 ```
295
296 When the return StructType of a `compute()` function uses AffineMapAttr to
297 express struct parameter(s) that depend on a loop variable, the optional
298 instantiation parameter list of this operation must be used to instatiate
299 all AffineMap used as parameters to the StructType.
300
301 Examples:
302 ```llzk
303 #M = affine_map<(i)[] -> (5*i+1)>
304 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
305 ```
306 }];
307
308 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
309 let arguments = (ins SymbolRefAttr:$callee,
310 Variadic<AnyLLZKType>:$argOperands,
311 // List of AffineMap operand groups where each group provides the
312 // arguments to instantiate the next (left-to-right) AffineMap used as a
313 // struct parameter in the result StructType.
314 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
315 // Within each group in '$mapOperands', denotes the number of values that
316 // are AffineMap "dimensional" arguments with the remaining values being
317 // AffineMap "symbolic" arguments.
318 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
319 // Denotes the size of each variadic group in '$mapOperands'.
320 DenseI32ArrayAttr:$mapOpGroupSizes);
321 let results = (outs Variadic<AnyLLZKType>);
322
323 // Define builders manually so inference of operand layout attributes is not
324 // circumvented.
325 let skipDefaultBuilders = 1;
326 let builders =
327 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
328 "::mlir::SymbolRefAttr":$callee,
329 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
330 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
331 "::mlir::SymbolRefAttr":$callee,
332 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
333 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
334 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
335 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
336 "::mlir::SymbolRefAttr":$callee,
337 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
338 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
339 CArg<"::mlir::ValueRange", "{}">:$argOperands),
340 [{
341 build($_builder, $_state, resultTypes, callee, mapOperands,
342 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
343 }]>,
344 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
345 CArg<"::mlir::ValueRange", "{}">:$argOperands),
346 [{
347 build($_builder, $_state, callee.getResultTypes(),
348 callee.getFullyQualifiedName(false), argOperands);
349 }]>,
350 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
351 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
352 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
353 CArg<"::mlir::ValueRange", "{}">:$argOperands),
354 [{
355 build($_builder, $_state, callee.getResultTypes(),
356 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap, argOperands);
357 }]>,
358 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
359 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
360 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
361 CArg<"::mlir::ValueRange", "{}">:$argOperands),
362 [{
363 build($_builder, $_state, callee, mapOperands,
364 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
365 }]>];
366
367 let extraClassDeclaration = [{
368 ::mlir::FunctionType getCalleeType();
369
370 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
371 /// does not check if the callee function is located within a StructDefOp).
372 inline bool calleeIsCompute() {
373 return FUNC_NAME_COMPUTE == getCallee().getLeafReference();
374 }
375
376 /// Return `true` iff the callee function can contain witness generation code
377 /// (this does not check if the callee function is located within a StructDefOp)
378 inline bool calleeContainsWitnessGen() {
379 return FUNC_NAME_COMPUTE == getCallee().getLeafReference() ||
380 FUNC_NAME_PRODUCT == getCallee().getLeafReference();
381 }
382
383 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
384 /// does not check if the callee function is located within a StructDefOp).
385 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
386
387 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
388 bool calleeIsStructCompute();
389
390 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
391 bool calleeIsStructConstrain();
392
393 /// Return the "self" value (i.e. the return value) from the callee function (which must be
394 /// named `FUNC_NAME_COMPUTE`).
395 ::mlir::Value getSelfValueFromCompute();
396
397 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
398 /// named `FUNC_NAME_CONSTRAIN`).
399 ::mlir::Value getSelfValueFromConstrain();
400
401 /// Resolve and return the target FuncDefOp for this CallOp.
402 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
403 getCalleeTarget(::mlir::SymbolTableCollection &tables);
404
405 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
406 ::llzk::component::StructType getSingleResultTypeOfCompute();
407
408 /// Assuming the callee contains witness generation code, return the single StructType result.
409 ::llzk::component::StructType getSingleResultTypeOfWitnessGen();
410
411 /// Allocate consecutive storage of the ValueRange instances in the parameter
412 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
413 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
414 }];
415
416 let assemblyFormat = [{
417 $callee `(` $argOperands `)`
418 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
419 `:` functional-type($argOperands, results)
420 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
421 }];
422
423 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
424 // call `verifyAffineMapInstantiations()`. However, in this op that check must
425 // happen within `verifySymbolUses()` instead because the target FuncDefOp
426 // must be resolved to determine if a target function named
427 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
428 // because the verification differs for those cases.
429}
430
431#endif // LLZK_FUNC_OPS