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 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
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
13//===----------------------------------------------------------------------===//
18include "llzk/Dialect/Function/IR/Dialect.td"
19include "llzk/Dialect/Shared/OpTraits.td"
20include "llzk/Dialect/Shared/CallLikeOpInterfaces.td"
21include "llzk/Dialect/Shared/Types.td"
22include "llzk/Dialect/Verif/IR/OpInterfaces.td"
24include "mlir/IR/OpAsmInterface.td"
25include "mlir/IR/SymbolInterfaces.td"
26include "mlir/Interfaces/CallInterfaces.td"
27include "mlir/Interfaces/ControlFlowInterfaces.td"
28include "mlir/Interfaces/FunctionInterfaces.td"
29include "mlir/Interfaces/InferTypeOpInterface.td"
30include "mlir/Interfaces/SideEffectInterfaces.td"
32class FunctionDialectOp<string mnemonic, list<Trait> traits = []>
33 : Op<FunctionDialect, mnemonic, traits>;
35//===----------------------------------------------------------------------===//
37//===----------------------------------------------------------------------===//
41 "def", [ParentOneOf<["::mlir::ModuleOp",
42 "::llzk::component::StructDefOp",
43 "::llzk::polymorphic::TemplateOp"]>,
44 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
45 AutomaticAllocationScope, FunctionOpInterface,
46 IsolatedFromAbove, ContractTarget]> {
47 // NOTE: Cannot have SymbolTable trait because that would cause global
48 // functions without a body to produce "Operations with a 'SymbolTable' must
49 // have exactly one block"
50 let summary = "An operation with a name containing a single `SSACFG` region";
52 Operations within the function cannot implicitly capture values defined
53 outside of the function, i.e., functions are `IsolatedFromAbove`. All
54 external references must use function arguments (which are passed by value)
55 or reference external members or globals by symbol name.
57 Functions appearing within a `struct.def` have specific semantics and must
58 be named `compute`, `constrain`, or `product`. Functions appearing at the
59 module level (i.e. not within a `struct.def`) have no name restrictions and
60 their body may be elided to denote an external function declaration.
62 Modules and `struct.def` ops are not allowed to be nested within functions.
64 Function arguments may carry an optional `function.arg_name` attribute to
65 preserve the source-level argument name independently from the printed SSA
66 block argument name. The value must be a non-empty, untyped `StringAttr`,
67 and all attached `function.arg_name` values must be unique within the
68 function. Typed string attributes such as `"x" : i1` are rejected. The
69 attribute is only valid on function arguments.
70 Argument-splitting transforms preserve this metadata by deriving unique
71 names for generated arguments, such as `input[0]` for array elements or
72 `self.member` for struct members.
74 Function results may similarly carry an optional `function.res_name`
75 attribute. The value must be a non-empty, untyped `StringAttr`, all attached
76 result names must be unique within the function, and the attribute is only
77 valid on function results.
82 // External function definitions.
83 function.def private @abort()
84 function.def private @scribble(
85 !array.type<5 x !felt.type> {function.arg_name = "input"},
86 !struct.type<@Hello> {function.arg_name = "state"}) -> i1
88 // A function that returns its argument twice:
89 function.def @count(%x: !felt.type {function.arg_name = "x"})
90 -> (!felt.type {function.res_name = "first"},
91 !felt.type {function.res_name = "second"}) {
92 function.return %x, %x: !felt.type, !felt.type
95 // Function definition within a component
97 function.def @compute(%a: !felt.type {function.arg_name = "a"}) { function.return }
98 function.def @constrain(%a: !felt.type {function.arg_name = "a"}) { function.return }
103 // Duplicated from the pre-defined `func` dialect. We don't store the
104 // visibility attribute but, since we use `function_interface_impl` for
105 // parsing/printing, there is still the requirement that global functions
106 // declared without a body must specify the `private` visibility.
107 // Additionally, the default parsing/printing functions allow attributes on
108 // the arguments, results, and function itself.
110 // // Argument attribute
111 // function.def private @example_fn_arg(%x: i1 {llzk.pub})
112 // function.def private @example_fn_arg_name(%x: i1 {function.arg_name =
115 // // Result attribute
116 // function.def @example_fn_result() -> (i1 {dialectName.attrName = 0 :
119 // // Function attribute
120 // function.def @example_fn_attr() attributes {dialectName.attrName =
123 let arguments = (ins SymbolNameAttr:$sym_name,
124 TypeAttrOf<FunctionType>:$function_type,
125 OptionalAttr<DictArrayAttr>:$arg_attrs,
126 OptionalAttr<DictArrayAttr>:$res_attrs);
127 let regions = (region AnyRegion:$body);
129 let builders = [OpBuilder<(ins "::llvm::StringRef":$name,
130 "::mlir::FunctionType":$type,
131 CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>", "{}">:$attrs,
132 CArg<"::llvm::ArrayRef<::mlir::DictionaryAttr>", "{}">:$argAttrs)>];
134 let extraClassDeclaration = [{
135 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
136 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs = {});
137 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
138 ::mlir::Operation::dialect_attr_range attrs);
139 static FuncDefOp create(::mlir::Location location, ::llvm::StringRef name, ::mlir::FunctionType type,
140 ::llvm::ArrayRef<::mlir::NamedAttribute> attrs,
141 ::llvm::ArrayRef<::mlir::DictionaryAttr> argAttrs);
143 /// Create a deep copy of this function and all of its blocks, remapping any
144 /// operands that use values outside of the function using the map that is
145 /// provided (leaving them alone if no entry is present). If the mapper
146 /// contains entries for function arguments, these arguments are not
147 /// included in the new function. Replaces references to cloned sub-values
148 /// with the corresponding value that is copied, and adds those mappings to
150 FuncDefOp clone(::mlir::IRMapping &mapper);
153 /// Clone the internal blocks and attributes from this function into dest.
154 /// Any cloned blocks are appended to the back of dest. This function
155 /// asserts that the attributes of the current function and dest are
157 void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper);
159 /// Return `true` iff the function def has the `allow_constraint` attribute.
160 inline bool hasAllowConstraintAttr() {
161 return getOperation()->hasAttr(llzk::function::AllowConstraintAttr::name);
164 /// Add (resp. remove) the `allow_constraint` attribute to (resp. from) the function def.
165 void setAllowConstraintAttr(bool newValue = true);
167 /// Return `true` iff the function def has the `allow_witness` attribute.
168 inline bool hasAllowWitnessAttr() {
169 return getOperation()->hasAttr(llzk::function::AllowWitnessAttr::name);
172 /// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
173 void setAllowWitnessAttr(bool newValue = true);
175 /// Return `true` iff the function def has the `allow_non_native_field_ops` attribute.
176 inline bool hasAllowNonNativeFieldOpsAttr() {
177 return getOperation()->hasAttr(llzk::function::AllowNonNativeFieldOpsAttr::name);
180 /// Add (resp. remove) the `allow_non_native_field_ops` attribute to (resp. from) the function def.
181 void setAllowNonNativeFieldOpsAttr(bool newValue = true);
183 /// Return `true` iff the function def has the `allow_verif_ops` attribute.
184 inline bool hasAllowVerifOpsAttr() {
185 return getOperation()->hasAttr(llzk::function::AllowVerifOpsAttr::name);
188 /// Add (resp. remove) the `allow_verif_ops` attribute to (resp. from) the function def.
189 void setAllowVerifOpsAttr(bool newValue = true);
191 /// Return `true` iff the argument at the given index has `pub` attribute.
192 bool hasArgPublicAttr(unsigned index);
194 /// Return `true` iff the argument at the given index has a `function.arg_name` attribute.
195 bool hasArgName(unsigned index);
197 /// Return the `function.arg_name` attribute for the argument at the given index.
198 ::std::optional<::mlir::StringAttr> getArgNameAttr(unsigned index);
200 /// Set the `function.arg_name` attribute for the argument at the given index.
201 void setArgNameAttr(unsigned index, const ::mlir::StringAttr &attr);
203 /// Set the `function.arg_name` attribute for the argument at the given index from a string.
204 void setArgName(unsigned index, ::llvm::StringRef name);
206 /// Return `true` iff the result at the given index has a `function.res_name` attribute.
207 bool hasResName(unsigned index);
209 /// Return the `function.res_name` attribute for the result at the given index.
210 ::std::optional<::mlir::StringAttr> getResNameAttr(unsigned index);
212 /// Set the `function.res_name` attribute for the result at the given index.
213 void setResNameAttr(unsigned index, const ::mlir::StringAttr &attr);
215 /// Set the `function.res_name` attribute for the result at the given index from a string.
216 void setResName(unsigned index, ::llvm::StringRef name);
218 /// Required by FunctionOpInterface.
219 /// Returns the region on the current operation that is callable. This may
220 /// return null in the case of an external callable object, e.g. an external
222 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
224 /// Required by FunctionOpInterface.
225 /// Returns the argument types of this function.
226 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
228 /// Required by FunctionOpInterface.
229 /// Returns the result types of this function.
230 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
232 /// Required by SymbolOpInterface.
233 bool isDeclaration() { return isExternal(); }
235 /// Return the full name for this function from the root module, including
236 /// all surrounding symbol table names (i.e., modules and structs).
237 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
239 /// Return `true` iff the function name is `FUNC_NAME_COMPUTE` (if needed, a check
240 /// that this FuncDefOp is located within a StructDefOp must be done separately).
241 inline bool nameIsCompute() { return FUNC_NAME_COMPUTE == getSymName(); }
243 /// Return `true` iff the function name is `FUNC_NAME_CONSTRAIN` (if needed, a
244 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
245 inline bool nameIsConstrain() { return FUNC_NAME_CONSTRAIN == getSymName(); }
247 /// Return `true` iff the function name is `FUNC_NAME_PRODUCT` (if needed, a
248 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
249 inline bool nameIsProduct() { return FUNC_NAME_PRODUCT == getSymName(); }
251 /// Return `true` iff the function is within a StructDefOp
252 inline bool isInStruct() { return ::llzk::component::isInStruct(*this); }
254 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
255 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
257 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
258 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
260 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_PRODUCT`.
261 inline bool isStructProduct() { return isInStruct() && nameIsProduct(); }
263 /// Return the "self" value (i.e. the return value) from the function (which must be
264 /// named `FUNC_NAME_COMPUTE`).
265 ::mlir::Value getSelfValueFromCompute();
267 /// Return the "self" value (i.e. the return value) from the function (which must be
268 /// named `FUNC_NAME_PRODUCT`).
269 ::mlir::Value getSelfValueFromProduct();
271 /// Return the "self" value (i.e. the first parameter) from the function (which must be
272 /// named `FUNC_NAME_CONSTRAIN`).
273 ::mlir::Value getSelfValueFromConstrain();
275 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
276 ::llzk::component::StructType getSingleResultTypeOfCompute();
279 let hasCustomAssemblyFormat = 1;
283//===----------------------------------------------------------------------===//
285//===----------------------------------------------------------------------===//
288 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
289 Pure, MemRefsNormalizable, ReturnLike,
291 let summary = "Function return operation";
293 The `function.return` operation represents a return operation within a function.
294 The operation takes variable number of operands and produces no results.
295 The operand number and types must match the signature of the function
296 that contains the operation.
301 function.def @foo() : (!felt.type, index) {
303 return %0, %1 : !felt.type, index
308 let arguments = (ins Variadic<AnyLLZKType>:$operands);
310 let builders = [OpBuilder<(ins), [{
311 build($_builder, $_state, ::mlir::ValueRange {});
314 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
318//===----------------------------------------------------------------------===//
320//===----------------------------------------------------------------------===//
323 : FunctionDialectOp<"call", [MemRefsNormalizable, AttrSizedOperandSegments,
324 DeclareOpInterfaceMethods<
325 LLZKCallLikeOpInterface, ["getSelfValue"]>,
326 VerifySizesForMultiAffineOps<1>]> {
327 let summary = "call operation";
329 The `function.call` operation represents a call to another function. The operands
330 and result types of the call must match the specified function type. The
331 callee is encoded as a symbol reference attribute named "callee" which must
332 be the full path to the target function from the root module (i.e., the module
333 containing the [llzk::LANG_ATTR_NAME] attribute).
337 // Call a global function defined in the root module.
338 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
339 %1, %2 = function.call @split(%x) : (index) -> (index, index)
341 // Call a function within a component
342 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
343 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
346 When the return StructType of a `compute()` function uses AffineMapAttr to
347 express struct parameter(s) that depend on a loop variable, the optional
348 instantiation parameter list of this operation must be used to instatiate
349 all AffineMap used as parameters to the StructType.
353 #M = affine_map<(i)[] -> (5*i+1)>
354 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
357 When the call targets a free function within a `poly.template` region, the optional
358 template parameter list can be used to instantiate all `poly.param` symbols within
359 the template. If all `poly.param` symbols are used within the function signature,
360 this can be elided. Otherwise, it is required to instantiate the function. The `?`
361 wildcard can be used for any `poly.param` with a `poly.tvar` type restriction, even
362 those that cannot be inferred from the function signature. The wildcard allows for
363 inference of the type within the function body itself during the flattening pass
364 but may fail if the type cannot be inferred from the function body.
367 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
369 // Call target function reference.
370 SymbolRefAttr:$callee,
371 // List of arguments to call the target function.
372 Variadic<AnyLLZKType>:$argOperands,
373 // List of parameters to instantiate all `poly.param` symbols when the
374 // callee is a free function inside a `poly.template` region.
375 OptionalAttr<ArrayAttr>:$templateParams,
376 // List of AffineMap operand groups where each group provides the
377 // arguments to instantiate the next (left-to-right) AffineMap used as a
378 // struct parameter in the result StructType.
379 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
380 // Within each group in '$mapOperands', denotes the number of values that
381 // are AffineMap "dimensional" arguments with the remaining values being
382 // AffineMap "symbolic" arguments.
383 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
384 // Denotes the size of each variadic group in '$mapOperands'.
385 DenseI32ArrayAttr:$mapOpGroupSizes,
386 // Per-call argument and result attribute dictionaries required by
388 OptionalAttr<DictArrayAttr>:$arg_attrs,
389 OptionalAttr<DictArrayAttr>:$res_attrs);
391 let results = (outs Variadic<AnyLLZKType>);
393 let assemblyFormat = [{
395 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
396 `` `(` $argOperands `)`
397 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
398 `:` functional-type($argOperands, results)
399 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
402 let useCustomPropertiesEncoding = 1;
404 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
405 // call `verifyAffineMapInstantiations()`. However, in this op that check must
406 // happen within `verifySymbolUses()` instead because the target FuncDefOp
407 // must be resolved to determine if a target function named
408 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
409 // because the verification differs for those cases.
411 // Define builders manually so inference of operand layout attributes is not
413 let skipDefaultBuilders = 1;
415 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
416 "::mlir::SymbolRefAttr":$callee,
417 CArg<"::mlir::ValueRange", "{}">:$argOperands,
418 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
419 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
420 "::mlir::SymbolRefAttr":$callee,
421 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
422 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
423 CArg<"::mlir::ValueRange", "{}">:$argOperands,
424 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
425 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
426 "::mlir::SymbolRefAttr":$callee,
427 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
428 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
429 CArg<"::mlir::ValueRange", "{}">:$argOperands,
430 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
431 "{}">:$templateParams),
433 build($_builder, $_state, resultTypes, callee, mapOperands,
434 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
435 argOperands, templateParams);
437 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
438 CArg<"::mlir::ValueRange", "{}">:$argOperands,
439 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
440 "{}">:$templateParams),
442 build($_builder, $_state, callee.getResultTypes(),
443 callee.getFullyQualifiedName(false),
444 argOperands, templateParams);
446 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
447 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
448 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
449 CArg<"::mlir::ValueRange", "{}">:$argOperands,
450 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
451 "{}">:$templateParams),
453 build($_builder, $_state, callee.getResultTypes(),
454 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
455 argOperands, templateParams);
457 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
458 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
459 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
460 CArg<"::mlir::ValueRange", "{}">:$argOperands,
461 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
462 "{}">:$templateParams),
464 build($_builder, $_state, callee, mapOperands,
465 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
466 argOperands, templateParams);
469 let extraClassDeclaration = [{
470 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
471 /// does not check if the callee function is located within a StructDefOp).
472 inline bool calleeIsCompute() {
473 return FUNC_NAME_COMPUTE == getCallee().getLeafReference();
476 /// Return `true` iff the callee function name is `FUNC_NAME_PRODUCT` (this
477 /// does not check if the callee function is located within a StructDefOp).
478 inline bool calleeIsProduct() {
479 return FUNC_NAME_PRODUCT == getCallee().getLeafReference();
482 /// Return `true` iff the callee function can contain witness generation code
483 /// (this does not check if the callee function is located within a StructDefOp)
484 inline bool calleeContainsWitnessGen() {
485 return calleeIsCompute() || calleeIsProduct();
488 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
489 /// does not check if the callee function is located within a StructDefOp).
490 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
492 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
493 bool calleeIsStructCompute();
495 /// Return `true` iff the callee function name is `FUNC_NAME_PRODUCT` within a StructDefOp.
496 bool calleeIsStructProduct();
498 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
499 bool calleeIsStructConstrain();
501 /// Return `true` iff the callee function is within a StructDefOp.
502 inline bool calleeIsInStruct() {
503 return calleeIsStructCompute() || calleeIsStructConstrain() || calleeIsStructProduct();
506 /// Return the "self" value (i.e. the return value) from the callee function (which must be
507 /// named `FUNC_NAME_COMPUTE`).
508 ::mlir::Value getSelfValueFromCompute();
510 /// Return the "self" value (i.e. the return value) from the callee function (which must be
511 /// named `FUNC_NAME_PRODUCT`).
512 ::mlir::Value getSelfValueFromProduct();
514 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
515 /// named `FUNC_NAME_CONSTRAIN`).
516 ::mlir::Value getSelfValueFromConstrain();
518 /// Resolve and return the target FuncDefOp for this CallOp.
519 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
520 getCalleeTarget(::mlir::SymbolTableCollection &tables);
522 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
523 ::llzk::component::StructType getSingleResultTypeOfCompute();
525 /// Assuming the callee contains witness generation code, return the single StructType result.
526 ::llzk::component::StructType getSingleResultTypeOfWitnessGen();
531#endif // LLZK_FUNC_OPS