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 "::llzk::polymorphic::TemplateOp"]>,
42 DeclareOpInterfaceMethods<SymbolUserOpInterface>, AffineScope,
43 AutomaticAllocationScope, FunctionOpInterface, IsolatedFromAbove]> {
44 // NOTE: Cannot have SymbolTable trait because that would cause global
45 // functions without a body to produce "Operations with a 'SymbolTable' must
46 // have exactly one block"
47 let summary = "An operation with a name containing a single `SSACFG` region";
49 Operations within the function cannot implicitly capture values defined
50 outside of the function, i.e., functions are `IsolatedFromAbove`. All
51 external references must use function arguments (which are passed by value)
52 or reference external members or globals by symbol name.
54 Functions appearing within a `struct.def` have specific semantics and must
55 be named `compute`, `constrain`, or `product`. Functions appearing at the
56 module level (i.e. not within a `struct.def`) have no name restrictions and
57 their body may be elided to denote an external function declaration.
59 Modules and `struct.def` ops are not allowed to be nested within functions.
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 function def has the `allow_non_native_field_ops` attribute.
152 inline bool hasAllowNonNativeFieldOpsAttr() {
153 return getOperation()->hasAttr(llzk::function::AllowNonNativeFieldOpsAttr::name);
156 /// Add (resp. remove) the `allow_non_native_field_ops` attribute to (resp. from) the function def.
157 void setAllowNonNativeFieldOpsAttr(bool newValue = true);
159 /// Return `true` iff the argument at the given index has `pub` attribute.
160 bool hasArgPublicAttr(unsigned index);
162 /// Required by FunctionOpInterface.
163 /// Returns the region on the current operation that is callable. This may
164 /// return null in the case of an external callable object, e.g. an external
166 ::mlir::Region *getCallableRegion() { return isExternal() ? nullptr : &getBody(); }
168 /// Required by FunctionOpInterface.
169 /// Returns the argument types of this function.
170 ::llvm::ArrayRef<::mlir::Type> getArgumentTypes() { return getFunctionType().getInputs(); }
172 /// Required by FunctionOpInterface.
173 /// Returns the result types of this function.
174 ::llvm::ArrayRef<::mlir::Type> getResultTypes() { return getFunctionType().getResults(); }
176 /// Required by SymbolOpInterface.
177 bool isDeclaration() { return isExternal(); }
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 name is `FUNC_NAME_PRODUCT` (if needed, a
192 /// check that this FuncDefOp is located within a StructDefOp must be done separately).
193 inline bool nameIsProduct() { return FUNC_NAME_PRODUCT == getSymName(); }
195 /// Return `true` iff the function is within a StructDefOp
196 inline bool isInStruct() { return ::llzk::component::isInStruct(*this); }
198 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_COMPUTE`.
199 inline bool isStructCompute() { return isInStruct() && nameIsCompute(); }
201 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_CONSTRAIN`.
202 inline bool isStructConstrain() { return isInStruct() && nameIsConstrain(); }
204 /// Return `true` iff the function is within a StructDefOp and named `FUNC_NAME_PRODUCT`.
205 inline bool isStructProduct() { return isInStruct() && nameIsProduct(); }
207 /// Return the "self" value (i.e. the return value) from the function (which must be
208 /// named `FUNC_NAME_COMPUTE`).
209 ::mlir::Value getSelfValueFromCompute();
211 /// Return the "self" value (i.e. the first parameter) from the function (which must be
212 /// named `FUNC_NAME_CONSTRAIN`).
213 ::mlir::Value getSelfValueFromConstrain();
215 /// Assuming the name is `FUNC_NAME_COMPUTE`, return the single StructType result.
216 ::llzk::component::StructType getSingleResultTypeOfCompute();
219 let hasCustomAssemblyFormat = 1;
223//===----------------------------------------------------------------------===//
225//===----------------------------------------------------------------------===//
228 : FunctionDialectOp<"return", [HasParent<"::llzk::function::FuncDefOp">,
229 Pure, MemRefsNormalizable, ReturnLike,
231 let summary = "Function return operation";
233 The `function.return` operation represents a return operation within a function.
234 The operation takes variable number of operands and produces no results.
235 The operand number and types must match the signature of the function
236 that contains the operation.
241 function.def @foo() : (!felt.type, index) {
243 return %0, %1 : !felt.type, index
248 let arguments = (ins Variadic<AnyLLZKType>:$operands);
250 let builders = [OpBuilder<(ins), [{
251 build($_builder, $_state, std::nullopt);
254 let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
258//===----------------------------------------------------------------------===//
260//===----------------------------------------------------------------------===//
262def CallOp : FunctionDialectOp<
263 "call", [MemRefsNormalizable, AttrSizedOperandSegments,
264 VerifySizesForMultiAffineOps<1>,
265 DeclareOpInterfaceMethods<CallOpInterface>,
266 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
267 let summary = "call operation";
269 The `function.call` operation represents a call to another function. The operands
270 and result types of the call must match the specified function type. The
271 callee is encoded as a symbol reference attribute named "callee" which must
272 be the full path to the target function from the root module (i.e., the module
273 containing the [llzk::LANG_ATTR_NAME] attribute).
277 // Call a global function defined in the root module.
278 function.call @do_stuff(%0) : (!struct.type<@Bob>) -> ()
279 %1, %2 = function.call @split(%x) : (index) -> (index, index)
281 // Call a function within a component
282 %2 = function.call @OtherStruct::@compute(%3, %4) : (index, index) -> !struct.type<@OtherStruct>
283 function.call @OtherStruct::@constrain(%5, %6) : (!struct.type<@OtherStruct>, !felt.type) -> ()
286 When the return StructType of a `compute()` function uses AffineMapAttr to
287 express struct parameter(s) that depend on a loop variable, the optional
288 instantiation parameter list of this operation must be used to instatiate
289 all AffineMap used as parameters to the StructType.
293 #M = affine_map<(i)[] -> (5*i+1)>
294 %r = function.call @A::@compute(%x){(%i)} : (!felt.type) -> !struct.type<@A<[#M]>>
298 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
300 // Call target function reference.
301 SymbolRefAttr:$callee,
302 // List of arguments to call the target function.
303 Variadic<AnyLLZKType>:$argOperands,
304 // List of parameters to instantiate all `poly.param` symbols when the
305 // callee is a free function inside a `poly.template` region. If all
306 // `poly.param` symbols are used within the function signature, this can
307 // be elided. Otherwise, it is required to instantiate the function.
308 OptionalAttr<ArrayAttr>:$templateParams,
309 // List of AffineMap operand groups where each group provides the
310 // arguments to instantiate the next (left-to-right) AffineMap used as a
311 // struct parameter in the result StructType.
312 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
313 // Within each group in '$mapOperands', denotes the number of values that
314 // are AffineMap "dimensional" arguments with the remaining values being
315 // AffineMap "symbolic" arguments.
316 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
317 // Denotes the size of each variadic group in '$mapOperands'.
318 DenseI32ArrayAttr:$mapOpGroupSizes);
319 let results = (outs Variadic<AnyLLZKType>);
321 let assemblyFormat = [{
323 ( `<` custom<TemplateParams>($templateParams)^ `>` )?
324 `` `(` $argOperands `)`
325 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
326 `:` functional-type($argOperands, results)
327 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
330 let useCustomPropertiesEncoding = 1;
332 // NOTE: In CreateArrayOp, the `verify()` function is declared in order to
333 // call `verifyAffineMapInstantiations()`. However, in this op that check must
334 // happen within `verifySymbolUses()` instead because the target FuncDefOp
335 // must be resolved to determine if a target function named
336 // "compute"/"constrain" is defined within a StructDefOp or within a ModuleOp
337 // because the verification differs for those cases.
339 // Define builders manually so inference of operand layout attributes is not
341 let skipDefaultBuilders = 1;
343 [OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
344 "::mlir::SymbolRefAttr":$callee,
345 CArg<"::mlir::ValueRange", "{}">:$argOperands,
346 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
347 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
348 "::mlir::SymbolRefAttr":$callee,
349 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
350 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
351 CArg<"::mlir::ValueRange", "{}">:$argOperands,
352 CArg<"::llvm::ArrayRef<::mlir::Attribute>", "{}">:$templateParams)>,
353 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
354 "::mlir::SymbolRefAttr":$callee,
355 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
356 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
357 CArg<"::mlir::ValueRange", "{}">:$argOperands,
358 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
359 "{}">:$templateParams),
361 build($_builder, $_state, resultTypes, callee, mapOperands,
362 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
363 argOperands, templateParams);
365 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
366 CArg<"::mlir::ValueRange", "{}">:$argOperands,
367 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
368 "{}">:$templateParams),
370 build($_builder, $_state, callee.getResultTypes(),
371 callee.getFullyQualifiedName(false),
372 argOperands, templateParams);
374 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
375 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
376 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
377 CArg<"::mlir::ValueRange", "{}">:$argOperands,
378 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
379 "{}">:$templateParams),
381 build($_builder, $_state, callee.getResultTypes(),
382 callee.getFullyQualifiedName(false), mapOperands, numDimsPerMap,
383 argOperands, templateParams);
385 OpBuilder<(ins "::llzk::function::FuncDefOp":$callee,
386 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
387 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
388 CArg<"::mlir::ValueRange", "{}">:$argOperands,
389 CArg<"::llvm::ArrayRef<::mlir::Attribute>",
390 "{}">:$templateParams),
392 build($_builder, $_state, callee, mapOperands,
393 $_builder.getDenseI32ArrayAttr(numDimsPerMap),
394 argOperands, templateParams);
397 let extraClassDeclaration = [{
398 /// Required by CallOpInterface
399 ::mlir::Operation *resolveCallableInTable(::mlir::SymbolTableCollection *symbolTable);
401 /// Required by CallOpInterface
402 ::mlir::Operation *resolveCallable();
404 /// Return the FunctionType inferred from the arg operands and result types of this CallOp.
405 /// This is not necessarily the same as the callee's FunctionType but should unify with it
406 /// or else IR verification will fail.
407 ::mlir::FunctionType getTypeSignature();
409 /// Attempt type unfication between the inferred FunctionType from this CallOp (as LHS) and
410 /// the given FunctionType (as RHS). If successful, return a UnificationMap containing the
411 /// unifications that were made. Otherwise, return failure.
412 ::mlir::FailureOr<UnificationMap> unifyTypeSignature(::mlir::FunctionType other);
414 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` (this
415 /// does not check if the callee function is located within a StructDefOp).
416 inline bool calleeIsCompute() {
417 return FUNC_NAME_COMPUTE == getCallee().getLeafReference();
420 /// Return `true` iff the callee function can contain witness generation code
421 /// (this does not check if the callee function is located within a StructDefOp)
422 inline bool calleeContainsWitnessGen() {
423 return FUNC_NAME_COMPUTE == getCallee().getLeafReference() ||
424 FUNC_NAME_PRODUCT == getCallee().getLeafReference();
427 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` (this
428 /// does not check if the callee function is located within a StructDefOp).
429 inline bool calleeIsConstrain() { return FUNC_NAME_CONSTRAIN == getCallee().getLeafReference(); }
431 /// Return `true` iff the callee function name is `FUNC_NAME_COMPUTE` within a StructDefOp.
432 bool calleeIsStructCompute();
434 /// Return `true` iff the callee function name is `FUNC_NAME_CONSTRAIN` within a StructDefOp.
435 bool calleeIsStructConstrain();
437 /// Return the "self" value (i.e. the return value) from the callee function (which must be
438 /// named `FUNC_NAME_COMPUTE`).
439 ::mlir::Value getSelfValueFromCompute();
441 /// Return the "self" value (i.e. the first parameter) from the callee function (which must be
442 /// named `FUNC_NAME_CONSTRAIN`).
443 ::mlir::Value getSelfValueFromConstrain();
445 /// Resolve and return the target FuncDefOp for this CallOp.
446 ::mlir::FailureOr<::llzk::SymbolLookupResult<::llzk::function::FuncDefOp>>
447 getCalleeTarget(::mlir::SymbolTableCollection &tables);
449 /// Assuming the callee is `FUNC_NAME_COMPUTE`, return the single StructType result.
450 ::llzk::component::StructType getSingleResultTypeOfCompute();
452 /// Assuming the callee contains witness generation code, return the single StructType result.
453 ::llzk::component::StructType getSingleResultTypeOfWitnessGen();
455 /// Allocate consecutive storage of the ValueRange instances in the parameter
456 /// so it can be passed to the builders as an `ArrayRef<ValueRange>`.
457 static ::llvm::SmallVector<::mlir::ValueRange> toVectorOfValueRange(::mlir::OperandRangeRange);
459 /// Check type compatibility of the given template parameter value from this `CallOp` against
460 /// the declared type on the given `TemplateParamOp` (if any).
461 ::mlir::LogicalResult verifyTemplateParamCompatibility(
462 ::mlir::Attribute paramFromCallOp, ::llzk::polymorphic::TemplateParamOp targetParam
465 /// Check type compatibility of each template parameter value provided in this `CallOp` against
466 /// the declared type on each `TemplateParamOp` (if any).
468 /// Pre-condition assertions:
469 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
470 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
471 ::mlir::LogicalResult verifyTemplateParamCompatibility(
472 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs
475 /// Verify that each template parameter value provided in this `CallOp` is consistent with
476 /// the value inferred for the target `TemplateParamOp` in the given `UnificationMap`. The
477 /// `UnificationMap` is expected to contain the unification results of this `CallOp` against
478 /// the target function type signature.
480 /// Pre-condition assertions:
481 /// - `!isNullOrEmpty(getTemplateParamsAttr())`
482 /// - `getTemplateParamsAttr().size() == llvm::range_size(targetParamDefs)`
483 ::mlir::LogicalResult verifyTemplateParamsMatchInferred(
484 ::llvm::iterator_range<::mlir::Region::op_iterator<::llzk::polymorphic::TemplateParamOp>> targetParamDefs,
485 const UnificationMap &unifications
490#endif // LLZK_FUNC_OPS