LLZK 0.1.0
Veridise's ZK Language IR
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 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 argument at the given index has `pub` attribute.
152 bool hasArgPublicAttr(unsigned index);
153
154 //===------------------------------------------------------------------===//
155 // FunctionOpInterface Methods
156 //===------------------------------------------------------------------===//
157
158 /// Returns the region on the current operation that is callable. This may
159 /// return null in the case of an external callable object, e.g. an external
160 /// function.
161 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
162
163 /// Returns the argument types of this function.
164 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
165
166 /// Returns the result types of this function.
167 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
168
169 //===------------------------------------------------------------------===//
170 // SymbolOpInterface Methods
171 //===------------------------------------------------------------------===//
172
173 bool isDeclaration() { return isExternal(); }
174
175 //===------------------------------------------------------------------===//
176 // Utility Methods
177 //===------------------------------------------------------------------===//
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();
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 is within a StructDefOp
192 bool isInStruct() { return ::llzk::component::isInStruct(*this); }
193
194 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
195 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
196
197 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
198 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
199
200 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
201 ::llzk::component::StructType getSingleResultTypeOfCompute();
202 }];
203
204 let hasCustomAssemblyFormat = 1;
205 let hasVerifier = 1;
206}
207
208//===----------------------------------------------------------------------===//
209// ReturnOp
210//===----------------------------------------------------------------------===//
211
212def ReturnOp
213 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
214 Pure, MemRefsNormalizable, ReturnLike,
215 Terminator]> {
216 let summary = "Function return operation";
217 let description = [{
218 The `function.return` operation represents a return operation within a function.
219 The operation takes variable number of operands and produces no results.
220 The operand number and types must match the signature of the function
221 that contains the operation.
222
223 Example:
224
225 ```llzk
226 function.def @foo() : (!felt.type, index) {
227 ...
228 return %0, %1 : !felt.type, index
229 }
230 ```
231 }];
232
233 let arguments = (ins Variadic<AnyLLZKType>:$operands);
234
235 let builders = [OpBuilder<(ins), [{
236 build($_builder, $_state, std::nullopt);
237 }]>];
238
239 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
240 let hasVerifier = 1;
241}
242
243//===----------------------------------------------------------------------===//
244// CallOp
245//===----------------------------------------------------------------------===//
246
247def CallOp : FunctionDialectOp<
248 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
249 VerifySizesForMultiAffineOps<1>,
250 DeclareOpInterfaceMethods<CallOpInterface>,
251 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
252 let summary = "call operation";
253 let description = [{
254 The `function.call` operation represents a call to another function. The operands
255 and result types of the call must match the specified function type. The
256 callee is encoded as a symbol reference attribute named "callee" which must
257 be the full path to the target function from the root module (i.e. the module
258 containing the [llzk::LANG_ATTR_NAME] attribute).
259
260 Example:
261 ```llzk
262 // Call a global function defined in the root module.
263 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
264 %1, %2 = function.call @split(%x) : (index) -> (index, index)
265
266 // Call a function within a component
267 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
268 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
269 ```
270
271 When the return StructType of a `compute()` function uses AffineMapAttr to
272 express struct parameter(s) that depend on a loop variable, the optional
273 instantiation parameter list of this operation must be used to instatiate
274 all AffineMap used as parameters to the StructType.
275
276 Examples:
277 ```llzk
278 #M = affine_map<(i)[] -> (5*i+1)>
279 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
280 ```
281 }];
282
283 let arguments = (ins SymbolRefAttr:$callee,
284 Variadic<AnyLLZKType>:$argOperands,
285 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
286 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
287 DenseI32ArrayAttr:$mapOpGroupSizes);
288 let results = (outs Variadic<AnyLLZKType>);
289
290 // Define builders manually so inference of operand layout attributes is not
291 // circumvented.
292 let skipDefaultBuilders = 1;
293 let builders =
294 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
295 "::mlir::SymbolRefAttr":$callee,
296 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
297 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
298 CArg<"::mlir::ValueRange", "{}">:$argOperands),
299 [{
300 build($_builder, $_state, callee.getFunctionType().getResults(),
301 ::mlir::SymbolRefAttr::get(callee), argOperands);
302 }]>,
303 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
304 "::mlir::SymbolRefAttr":$callee,
305 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
306 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
307 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
308 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
309 "::mlir::SymbolRefAttr":$callee,
310 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
311 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
312 CArg<"::mlir::ValueRange", "{}">:$argOperands),
313 [{
314 build($_builder, $_state, resultTypes, callee, mapOperands,
315 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
316 }]>,
317 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
318 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
319 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
320 CArg<"::mlir::ValueRange", "{}">:$argOperands),
321 [{
322 build($_builder, $_state, callee.getFunctionType().getResults(),
323 ::mlir::SymbolRefAttr::get(callee), mapOperands, numDimsPerMap, argOperands);
324 }]>,
325 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
326 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
327 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
328 CArg<"::mlir::ValueRange", "{}">:$argOperands),
329 [{
330 build($_builder, $_state, callee, mapOperands,
331 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
332 }]>];
333
334 let extraClassDeclaration = [{
335 ::mlir::FunctionType getCalleeType();
336
337 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
338 /// does not check if the callee function is located within a StructDefOp).
339 inline bool calleeIsCompute() { return FUNC_NAME_COMPUTE == getCallee().getLeafReference(); }
340
341 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
342 /// does not check if the callee function is located within a StructDefOp).
343 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
344
345 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
346 bool calleeIsStructCompute();
347
348 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
349 bool calleeIsStructConstrain();
350
351 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
352 ::llzk::component::StructType getSingleResultTypeOfCompute();
353 }];
354
355 let assemblyFormat = [{
356 $callee `(` $argOperands `)`
357 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
358 `:` functional-type($argOperands, results)
359 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
360 }];
361
362 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
363 // call `verifyAffineMapInstantiations()`. However, in this op that check must
364 // happen within `verifySymbolUses()` instead because the target FuncDefOp
365 // must be resolved to determine if a target function named
366 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
367 // because the verification differs for those cases.
368}
369
370#endif // LLZK_FUNC_OPS