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(bool requireParent = true);
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 inline 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 /// Return the "self" value (i.e. the return value) from the function (which must be
201 /// named `FUNC_NAME_COMPUTE`).
202 ::mlir::Value getSelfValueFromCompute();
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();
208 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
209 ::llzk::component::StructType getSingleResultTypeOfCompute();
212 let hasCustomAssemblyFormat = 1;
216//===----------------------------------------------------------------------===//
218//===----------------------------------------------------------------------===//
221 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
222 Pure, MemRefsNormalizable, ReturnLike,
224 let summary = "Function return operation";
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.
234 function.def @foo() : (!felt.type, index) {
236 return %0, %1 : !felt.type, index
241 let arguments = (ins Variadic<AnyLLZKType>:$operands);
243 let builders = [OpBuilder<(ins), [{
244 build($_builder, $_state, std::nullopt);
247 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
251//===----------------------------------------------------------------------===//
253//===----------------------------------------------------------------------===//
255def CallOp : FunctionDialectOp<
256 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
257 VerifySizesForMultiAffineOps<1>,
258 DeclareOpInterfaceMethods<CallOpInterface>,
259 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
260 let summary = "call operation";
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).
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)
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) -> ()
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.
286 #M = affine_map<(i)[] -> (5*i+1)>
287 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
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>);
298 // Define builders manually so inference of operand layout attributes is not
300 let skipDefaultBuilders = 1;
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),
316 build($_builder, $_state, resultTypes, callee, mapOperands,
317 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
319 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
320 CArg<"::mlir::ValueRange", "{}">:$argOperands),
322 build($_builder, $_state, callee.getResultTypes(),
323 callee.getFullyQualifiedName(false), argOperands);
325 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
326 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
327 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
328 CArg<"::mlir::ValueRange", "{}">:$argOperands),
330 build($_builder, $_state, callee.getResultTypes(),
331 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap, argOperands);
333 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
334 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
335 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
336 CArg<"::mlir::ValueRange", "{}">:$argOperands),
338 build($_builder, $_state, callee, mapOperands,
339 $_builder.getDenseI32ArrayAttr(numDimsPerMap), argOperands);
342 let extraClassDeclaration = [{
343 ::mlir::FunctionType getCalleeType();
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(); }
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(); }
353 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
354 bool calleeIsStructCompute();
356 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
357 bool calleeIsStructConstrain();
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();
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();
367 /// Resolve and return the target FuncDefOp for this CallOp.
368 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
369 getCalleeTarget(::mlir::SymbolTableCollection &tables);
371 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
372 ::llzk::component::StructType getSingleResultTypeOfCompute();
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);
379 let assemblyFormat = [{
380 $callee `(` $argOperands `)`
381 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
382 `:` functional-type($argOperands, results)
383 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
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.
394#endif // LLZK_FUNC_OPS