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.
61 Modules and struct definitions are not allowed to be nested within functions.
66 // External function definitions.
67 function.def private @abort()
68 function.def private @scribble(!array.type<5 x !felt.type>, !struct.type<@Hello>) -> i1
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
75 // Function definition within a component
77 function.def @compute(%a: !felt.type) { return }
78 function.def @constrain(%a: !felt.type) { return }
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.
90 // // Argument attribute
91 // function.def private @example_fn_arg(%x: i1 {llzk.pub})
93 // // Result attribute
94 // function.def @example_fn_result() -> (i1 {dialectName.attrName = 0 :
97 // // Function attribute
98 // function.def @example_fn_attr() attributes {dialectName.attrName =
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);
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)>];
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);
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
128 FuncDefOp clone(::mlir::IRMapping &mapper);
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
135 void cloneInto(FuncDefOp dest, ::mlir::IRMapping &mapper);
137 /// Return `true` iff the function def has the `allow_constraint` attribute.
138 inline bool hasAllowConstraintAttr() {
139 return getOperation()->hasAttr(llzk::function::AllowConstraintAttr::name);
142 /// Add (resp. remove) the `allow_constraint` attribute to (resp. from) the function def.
143 void setAllowConstraintAttr(bool newValue = true);
145 /// Return `true` iff the function def has the `allow_witness` attribute.
146 inline bool hasAllowWitnessAttr() {
147 return getOperation()->hasAttr(llzk::function::AllowWitnessAttr::name);
150 /// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
151 void setAllowWitnessAttr(bool newValue = true);
153 /// Return `true` iff the argument at the given index has `pub` attribute.
154 bool hasArgPublicAttr(unsigned index);
156 //===------------------------------------------------------------------===//
157 // FunctionOpInterface Methods
158 //===------------------------------------------------------------------===//
160 /// Returns the region on the current operation that is callable. This may
161 /// return null in the case of an external callable object, e.g. an external
163 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
165 /// Returns the argument types of this function.
166 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
168 /// Returns the result types of this function.
169 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
171 //===------------------------------------------------------------------===//
172 // SymbolOpInterface Methods
173 //===------------------------------------------------------------------===//
175 bool isDeclaration() { return isExternal(); }
177 //===------------------------------------------------------------------===//
179 //===------------------------------------------------------------------===//
181 /// Return the full name for this function from the root module, including
182 /// all surrounding symbol table names (i.e., modules and structs).
183 ::mlir::SymbolRefAttr getFullyQualifiedName(bool requireParent = true);
185 /// Return `true` iff the function name is `FUNC_NAME_COMPUTE` (if needed, a check
186 /// that this FuncDefOp is located within a StructDefOp must be done separately).
187 inline bool nameIsCompute() { return FUNC_NAME_COMPUTE == getSymName(); }
189 /// Return `true` iff the function name is `FUNC_NAME_CONSTRAIN` (if needed, a
190 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
191 inline bool nameIsConstrain() { return FUNC_NAME_CONSTRAIN == getSymName(); }
193 /// Return `true` iff the function name is `FUNC_NAME_PRODUCT` (if needed, a
194 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
195 inline bool nameIsProduct() { return FUNC_NAME_PRODUCT == getSymName(); }
197 /// Return `true` iff the function is within a StructDefOp
198 inline bool isInStruct() { return ::llzk::component::isInStruct(*this); }
200 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
201 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
203 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
204 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
206 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_PRODUCT`.
207 inline bool isStructProduct() { return isInStruct() && nameIsProduct(); }
209 /// Return the "self" value (i.e. the return value) from the function (which must be
210 /// named `FUNC_NAME_COMPUTE`).
211 ::mlir::Value getSelfValueFromCompute();
213 /// Return the "self" value (i.e. the first parameter) from the function (which must be
214 /// named `FUNC_NAME_CONSTRAIN`).
215 ::mlir::Value getSelfValueFromConstrain();
217 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
218 ::llzk::component::StructType getSingleResultTypeOfCompute();
221 let hasCustomAssemblyFormat = 1;
225//===----------------------------------------------------------------------===//
227//===----------------------------------------------------------------------===//
230 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
231 Pure, MemRefsNormalizable, ReturnLike,
233 let summary = "Function return operation";
235 The `function.return` operation represents a return operation within a function.
236 The operation takes variable number of operands and produces no results.
237 The operand number and types must match the signature of the function
238 that contains the operation.
243 function.def @foo() : (!felt.type, index) {
245 return %0, %1 : !felt.type, index
250 let arguments = (ins Variadic<AnyLLZKType>:$operands);
252 let builders = [OpBuilder<(ins), [{
253 build($_builder, $_state, std::nullopt);
256 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
260//===----------------------------------------------------------------------===//
262//===----------------------------------------------------------------------===//
264def CallOp : FunctionDialectOp<
265 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
266 VerifySizesForMultiAffineOps<1>,
267 DeclareOpInterfaceMethods<CallOpInterface>,
268 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
269 let summary = "call operation";
271 The `function.call` operation represents a call to another function. The operands
272 and result types of the call must match the specified function type. The
273 callee is encoded as a symbol reference attribute named "callee" which must
274 be the full path to the target function from the root module (i.e., the module
275 containing the [llzk::LANG_ATTR_NAME] attribute).
279 // Call a global function defined in the root module.
280 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
281 %1, %2 = function.call @split(%x) : (index) -> (index, index)
283 // Call a function within a component
284 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
285 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
288 When the return StructType of a `compute()` function uses AffineMapAttr to
289 express struct parameter(s) that depend on a loop variable, the optional
290 instantiation parameter list of this operation must be used to instatiate
291 all AffineMap used as parameters to the StructType.
295 #M = affine_map<(i)[] -> (5*i+1)>
296 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
300 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
301 let arguments = (ins SymbolRefAttr:$callee,
302 Variadic<AnyLLZKType>:$argOperands,
303 // List of AffineMap operand groups where each group provides the
304 // arguments to instantiate the next (left-to-right) AffineMap used as a
305 // struct parameter in the result StructType.
306 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
307 // Within each group in '$mapOperands', denotes the number of values that
308 // are AffineMap "dimensional" arguments with the remaining values being
309 // AffineMap "symbolic" arguments.
310 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
311 // Denotes the size of each variadic group in '$mapOperands'.
312 DenseI32ArrayAttr:$mapOpGroupSizes);
313 let results = (outs Variadic<AnyLLZKType>);
315 // Define builders manually so inference of operand layout attributes is not
317 let skipDefaultBuilders = 1;
319 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
320 "::mlir::SymbolRefAttr":$callee,
321 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
322 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
323 "::mlir::SymbolRefAttr":$callee,
324 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
325 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
326 CArg<"::mlir::ValueRange", "{}">:$argOperands)>,
327 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
328 "::mlir::SymbolRefAttr":$callee,
329 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
330 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
331 CArg<"::mlir::ValueRange", "{}">:$argOperands),
333 build($_builder, $_state, resultTypes, callee, mapOperands,
334 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
336 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
337 CArg<"::mlir::ValueRange", "{}">:$argOperands),
339 build($_builder, $_state, callee.getResultTypes(),
340 callee.getFullyQualifiedName(false), argOperands);
342 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
343 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
344 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
345 CArg<"::mlir::ValueRange", "{}">:$argOperands),
347 build($_builder, $_state, callee.getResultTypes(),
348 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap, argOperands);
350 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
351 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
352 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
353 CArg<"::mlir::ValueRange", "{}">:$argOperands),
355 build($_builder, $_state, callee, mapOperands,
356 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
359 let extraClassDeclaration = [{
360 ::mlir::FunctionType getCalleeType();
362 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
363 /// does not check if the callee function is located within a StructDefOp).
364 inline bool calleeIsCompute() { return FUNC_NAME_COMPUTE == getCallee().getLeafReference(); }
366 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
367 /// does not check if the callee function is located within a StructDefOp).
368 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
370 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
371 bool calleeIsStructCompute();
373 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
374 bool calleeIsStructConstrain();
376 /// Return the "self" value (i.e. the return value) from the callee function (which must be
377 /// named `FUNC_NAME_COMPUTE`).
378 ::mlir::Value getSelfValueFromCompute();
380 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
381 /// named `FUNC_NAME_CONSTRAIN`).
382 ::mlir::Value getSelfValueFromConstrain();
384 /// Resolve and return the target FuncDefOp for this CallOp.
385 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
386 getCalleeTarget(::mlir::SymbolTableCollection &tables);
388 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
389 ::llzk::component::StructType getSingleResultTypeOfCompute();
391 /// Allocate consecutive storage of the ValueRange instances in the parameter
392 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
393 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
396 let assemblyFormat = [{
397 $callee `(` $argOperands `)`
398 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
399 `:` functional-type($argOperands, results)
400 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
403 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
404 // call `verifyAffineMapInstantiations()`. However, in this op that check must
405 // happen within `verifySymbolUses()` instead because the target FuncDefOp
406 // must be resolved to determine if a target function named
407 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
408 // because the verification differs for those cases.
411#endif // LLZK_FUNC_OPS