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(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 is within a StructDefOp
192 inline 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 /// Return the "self" value (i.e. the return value) from the function (which must be
201 /// named `FUNC_NAME_COMPUTE`).
202 ::mlir::Value getSelfValueFromCompute();
203
204 /// Return the "self" value (i.e. the first parameter) from the function (which must be
205 /// named `FUNC_NAME_CONSTRAIN`).
206 ::mlir::Value getSelfValueFromConstrain();
207
208 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
209 ::llzk::component::StructType getSingleResultTypeOfCompute();
210 }];
211
212 let hasCustomAssemblyFormat = 1;
213 let hasVerifier = 1;
214}
215
216//===----------------------------------------------------------------------===//
217// ReturnOp
218//===----------------------------------------------------------------------===//
219
220def ReturnOp
221 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
222 Pure, MemRefsNormalizable, ReturnLike,
223 Terminator]> {
224 let summary = "Function return operation";
225 let description = [{
226 The `function.return` operation represents a return operation within a function.
227 The operation takes variable number of operands and produces no results.
228 The operand number and types must match the signature of the function
229 that contains the operation.
230
231 Example:
232
233 ```llzk
234 function.def @foo() : (!felt.type, index) {
235 ...
236 return %0, %1 : !felt.type, index
237 }
238 ```
239 }];
240
241 let arguments = (ins Variadic<AnyLLZKType>:$operands);
242
243 let builders = [OpBuilder<(ins), [{
244 build($_builder, $_state, std::nullopt);
245 }]>];
246
247 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
248 let hasVerifier = 1;
249}
250
251//===----------------------------------------------------------------------===//
252// CallOp
253//===----------------------------------------------------------------------===//
254
255def CallOp : FunctionDialectOp<
256 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
257 VerifySizesForMultiAffineOps<1>,
258 DeclareOpInterfaceMethods<CallOpInterface>,
259 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
260 let summary = "call operation";
261 let description = [{
262 The `function.call` operation represents a call to another function. The operands
263 and result types of the call must match the specified function type. The
264 callee is encoded as a symbol reference attribute named "callee" which must
265 be the full path to the target function from the root module (i.e., the module
266 containing the [llzk::LANG_ATTR_NAME] attribute).
267
268 Example:
269 ```llzk
270 // Call a global function defined in the root module.
271 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
272 %1, %2 = function.call @split(%x) : (index) -> (index, index)
273
274 // Call a function within a component
275 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
276 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
277 ```
278
279 When the return StructType of a `compute()` function uses AffineMapAttr to
280 express struct parameter(s) that depend on a loop variable, the optional
281 instantiation parameter list of this operation must be used to instatiate
282 all AffineMap used as parameters to the StructType.
283
284 Examples:
285 ```llzk
286 #M = affine_map<(i)[] -> (5*i+1)>
287 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
288 ```
289 }];
290
291 let arguments = (ins SymbolRefAttr:$callee,
292 Variadic<AnyLLZKType>:$argOperands,
293 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
294 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
295 DenseI32ArrayAttr:$mapOpGroupSizes);
296 let results = (outs Variadic<AnyLLZKType>);
297
298 // Define builders manually so inference of operand layout attributes is not
299 // circumvented.
300 let skipDefaultBuilders = 1;
301 let builders =
302 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
303 "::mlir::SymbolRefAttr":$callee,
304 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
305 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
306 "::mlir::SymbolRefAttr":$callee,
307 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
308 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
309 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
310 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
311 "::mlir::SymbolRefAttr":$callee,
312 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
313 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
314 CArg<"::mlir::ValueRange", "{}">:$argOperands),
315 [{
316 build($_builder, $_state, resultTypes, callee, mapOperands,
317 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
318 }]>,
319 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
320 CArg<"::mlir::ValueRange", "{}">:$argOperands),
321 [{
322 build($_builder, $_state, callee.getResultTypes(),
323 callee.getFullyQualifiedName(false), argOperands);
324 }]>,
325 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
326 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
327 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
328 CArg<"::mlir::ValueRange", "{}">:$argOperands),
329 [{
330 build($_builder, $_state, callee.getResultTypes(),
331 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap, argOperands);
332 }]>,
333 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
334 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
335 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
336 CArg<"::mlir::ValueRange", "{}">:$argOperands),
337 [{
338 build($_builder, $_state, callee, mapOperands,
339 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
340 }]>];
341
342 let extraClassDeclaration = [{
343 ::mlir::FunctionType getCalleeType();
344
345 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
346 /// does not check if the callee function is located within a StructDefOp).
347 inline bool calleeIsCompute() { return FUNC_NAME_COMPUTE == getCallee().getLeafReference(); }
348
349 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
350 /// does not check if the callee function is located within a StructDefOp).
351 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
352
353 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
354 bool calleeIsStructCompute();
355
356 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
357 bool calleeIsStructConstrain();
358
359 /// Return the "self" value (i.e. the return value) from the callee function (which must be
360 /// named `FUNC_NAME_COMPUTE`).
361 ::mlir::Value getSelfValueFromCompute();
362
363 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
364 /// named `FUNC_NAME_CONSTRAIN`).
365 ::mlir::Value getSelfValueFromConstrain();
366
367 /// Resolve and return the target FuncDefOp for this CallOp.
368 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
369 getCalleeTarget(::mlir::SymbolTableCollection &tables);
370
371 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
372 ::llzk::component::StructType getSingleResultTypeOfCompute();
373
374 /// Allocate consecutive storage of the ValueRange instances in the parameter
375 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
376 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
377 }];
378
379 let assemblyFormat = [{
380 $callee `(` $argOperands `)`
381 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
382 `:` functional-type($argOperands, results)
383 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
384 }];
385
386 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
387 // call `verifyAffineMapInstantiations()`. However, in this op that check must
388 // happen within `verifySymbolUses()` instead because the target FuncDefOp
389 // must be resolved to determine if a target function named
390 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
391 // because the verification differs for those cases.
392}
393
394#endif // LLZK_FUNC_OPS