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/Types.td"
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"
30class FunctionDialectOp<string mnemonic, list<Trait> traits = []>
31 : Op<FunctionDialect, mnemonic, traits>;
33//===----------------------------------------------------------------------===//
35//===----------------------------------------------------------------------===//
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";
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
58 Only dialect attribute names may be specified in the attribute dictionaries
59 for function arguments, results, or the function itself.
64 // External function definitions.
65 function.def private @abort()
66 function.def private @scribble(!array.type<5 x !felt.type>, !struct.type<@Hello>) -> i1
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
73 // Function definition within a component
75 function.def @compute(%a: !felt.type) { return }
76 function.def @constrain(%a: !felt.type) { return }
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.
88 // // Argument attribute
89 // function.def private @example_fn_arg(%x: i1 {llzk.pub})
91 // // Result attribute
92 // function.def @example_fn_result() -> (i1 {dialectName.attrName = 0 :
95 // // Function attribute
96 // function.def @example_fn_attr() attributes {dialectName.attrName =
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);
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)>];
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);
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
126 FuncDefOp clone(::mlir::IRMapping &mapper);
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
133 void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper);
135 /// Return `true` iff the function def has the `allow_constraint` attribute.
136 inline bool hasAllowConstraintAttr() {
137 return getOperation()->hasAttr(llzk::function::AllowConstraintAttr::name);
140 /// Add (resp. remove) the `allow_constraint` attribute to (resp. from) the function def.
141 void setAllowConstraintAttr(bool newValue = true);
143 /// Return `true` iff the function def has the `allow_witness` attribute.
144 inline bool hasAllowWitnessAttr() {
145 return getOperation()->hasAttr(llzk::function::AllowWitnessAttr::name);
148 /// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
149 void setAllowWitnessAttr(bool newValue = true);
151 /// Return `true` iff the argument at the given index has `pub` attribute.
152 bool hasArgPublicAttr(unsigned index);
154 //===------------------------------------------------------------------===//
155 // FunctionOpInterface Methods
156 //===------------------------------------------------------------------===//
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
161 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
163 /// Returns the argument types of this function.
164 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
166 /// Returns the result types of this function.
167 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
169 //===------------------------------------------------------------------===//
170 // SymbolOpInterface Methods
171 //===------------------------------------------------------------------===//
173 bool isDeclaration() { return isExternal(); }
175 //===------------------------------------------------------------------===//
177 //===------------------------------------------------------------------===//
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();
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(); }
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(); }
191 /// Return `true` iff the function is within a StructDefOp
192 bool isInStruct() { return ::llzk::component::isInStruct(*this); }
194 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
195 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
197 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
198 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
200 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
201 ::llzk::component::StructType getSingleResultTypeOfCompute();
204 let hasCustomAssemblyFormat = 1;
208//===----------------------------------------------------------------------===//
210//===----------------------------------------------------------------------===//
213 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
214 Pure, MemRefsNormalizable, ReturnLike,
216 let summary = "Function return operation";
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.
226 function.def @foo() : (!felt.type, index) {
228 return %0, %1 : !felt.type, index
233 let arguments = (ins Variadic<AnyLLZKType>:$operands);
235 let builders = [OpBuilder<(ins), [{
236 build($_builder, $_state, std::nullopt);
239 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
243//===----------------------------------------------------------------------===//
245//===----------------------------------------------------------------------===//
247def CallOp : FunctionDialectOp<
248 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
249 VerifySizesForMultiAffineOps<1>,
250 DeclareOpInterfaceMethods<CallOpInterface>,
251 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
252 let summary = "call operation";
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).
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)
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) -> ()
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.
278 #M = affine_map<(i)[] -> (5*i+1)>
279 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
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>);
290 // Define builders manually so inference of operand layout attributes is not
292 let skipDefaultBuilders = 1;
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),
300 build($_builder, $_state, callee.getFunctionType().getResults(),
301 ::mlir::SymbolRefAttr::get(callee), argOperands);
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),
314 build($_builder, $_state, resultTypes, callee, mapOperands,
315 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
317 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
318 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
319 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
320 CArg<"::mlir::ValueRange", "{}">:$argOperands),
322 build($_builder, $_state, callee.getFunctionType().getResults(),
323 ::mlir::SymbolRefAttr::get(callee), mapOperands, numDimsPerMap, argOperands);
325 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
326 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
327 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
328 CArg<"::mlir::ValueRange", "{}">:$argOperands),
330 build($_builder, $_state, callee, mapOperands,
331 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
334 let extraClassDeclaration = [{
335 ::mlir::FunctionType getCalleeType();
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(); }
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(); }
345 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
346 bool calleeIsStructCompute();
348 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
349 bool calleeIsStructConstrain();
351 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
352 ::llzk::component::StructType getSingleResultTypeOfCompute();
355 let assemblyFormat = [{
356 $callee `(` $argOperands `)`
357 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
358 `:` functional-type($argOperands, results)
359 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
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.
370#endif // LLZK_FUNC_OPS