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//===----------------------------------------------------------------------===//
15#ifndef LLZK_STRUCT_OPS
16#define LLZK_STRUCT_OPS
18include "llzk/Dialect/Function/IR/OpTraits.td"
19include "llzk/Dialect/Struct/IR/Dialect.td"
20include "llzk/Dialect/Struct/IR/OpInterfaces.td"
21include "llzk/Dialect/Struct/IR/Types.td"
22include "llzk/Dialect/Shared/OpTraits.td"
24include "mlir/IR/OpAsmInterface.td"
25include "mlir/IR/RegionKindInterface.td"
26include "mlir/IR/SymbolInterfaces.td"
28class StructDialectOp<string mnemonic, list<Trait> traits = []>
29 : Op<StructDialect, mnemonic, traits>;
31/// Only valid/implemented for StructDefOp. Sets the proper `AllowWitnessAttr`
32/// and `AllowConstraintAttr` on the functions defined within the StructDefOp.
33def SetFuncAllowAttrs : NativeOpTrait<"SetFuncAllowAttrs">, StructuralOpTrait {
34 string cppNamespace = "::llzk::component";
37//===------------------------------------------------------------------===//
39//===------------------------------------------------------------------===//
43 "def", [HasParent<"::mlir::ModuleOp">, Symbol, SymbolTable,
44 IsolatedFromAbove, SetFuncAllowAttrs,
45 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
46 NoRegionArguments]#GraphRegionNoTerminator.traits> {
47 let summary = "circuit component definition";
49 This operation describes a component in a circuit. It can contain any number
50 of members that hold inputs, outputs, intermediate values, and subcomponents
51 of the defined component. It also contains a `compute()` function that holds
52 the witness generation code for the component and a `constrain()` function
53 that holds that constraint generation code for the component.
58 struct.def @ComponentA {
59 member @f1 : !array.type<5 x index>
60 member @f2 : !felt.type {llzk.pub}
62 function.def @compute(%p: !felt.type) -> !struct.type<@ComponentA> {
63 %self = struct.new : !struct.type<@ComponentA>
64 // initialize all members of `%self` here
65 return %self : !struct.type<@ComponentA>
68 function.def @constrain(%self: !struct.type<@ComponentA>, %p: !felt.type) {
69 // emit constraints here
75 The optional `llzk.main = !struct.type<...>` attribute on the top-level module in an
76 LLZK IR program expresses the main entry point of the circuit as a concrete instantiation
77 of a specific struct. For example, `llzk.main = !struct.type<@Top<[52,12]>>` or
78 `llzk.main = !struct.type<@Main<[i1, !felt.type, 256]>>`.
79 This component has additional restrictions:
80 1. The parameter types of its functions (besides the required "self" parameter) can
81 only be `!felt.type` or `!array.type<.. x !felt.type>`.
82 2. The members marked as public (i.e., with the `{llzk.pub}` attribute) represent
83 the public outputs of the overall circuit.
85 Example of a `Main` component:
89 member @out : !felt.type {llzk.pub} // public output
90 member @intermediate : !felt.type
91 function.def @compute(%p: !felt.type) -> !struct.type<@Main> {
94 function.def @constrain(%self: !struct.type<@Main>, %p: !felt.type) {
101 // Note: `$const_params` contains symbol definitions that do not use the
102 // standard SymbolTable mechanism. Instead hasParamNamed() can be used to
103 // check if a certain FlatSymbolRefAttr is a parameter in the function.
104 let arguments = (ins SymbolNameAttr:$sym_name,
105 OptionalAttr<FlatSymbolRefArrayAttr>:$const_params);
107 let regions = (region SizedRegion<1>:$bodyRegion);
109 let assemblyFormat = [{
110 $sym_name (`<` $const_params^ `>`)? $bodyRegion attr-dict
113 let extraClassDeclaration = [{
114 /// Gets the StructType representing this struct. If the `constParams` to use in
115 /// the type are not given, the StructType will use `this->getConstParamsAttr()`.
116 StructType getType(::std::optional<::mlir::ArrayAttr> constParams = {});
117 inline StructType getType(::std::optional<::mlir::ArrayAttr> constParams = {}) const {
118 return const_cast<StructDefOp*>(this)->getType(constParams);
121 /// Gets the MemberDefOp that defines the member in this
122 /// structure with the given name, if present.
123 MemberDefOp getMemberDef(::mlir::StringAttr memberName);
125 /// Get all MemberDefOp in this structure.
126 ::std::vector<MemberDefOp> getMemberDefs();
128 /// Returns whether the struct defines members marked as columns.
129 ::mlir::LogicalResult hasColumns() {
130 return ::mlir::success(::llvm::any_of(getMemberDefs(), [](MemberDefOp memberOp) {
131 return memberOp.getColumn();
135 /// Returns whether the struct defines members marked as signals.
136 ::mlir::LogicalResult hasSignals() {
137 return ::mlir::success(::llvm::any_of(getMemberDefs(), [](MemberDefOp memberOp) {
138 return memberOp.getSignal();
143 /// Gets the FuncDefOp that defines the compute function in this structure, if present, or `nullptr` otherwise.
144 ::llzk::function::FuncDefOp getComputeFuncOp();
146 /// Gets the FuncDefOp that defines the constrain function in this structure, if present, or `nullptr` otherwise.
147 ::llzk::function::FuncDefOp getConstrainFuncOp();
149 /// Gets the FuncDefOp that defines the compute function in this structure, if present, or the product function otherwise.
150 ::llzk::function::FuncDefOp getComputeOrProductFuncOp();
152 /// Gets the FuncDefOp that defines the constrain function in this structure, if present, or the product function otherwise.
153 ::llzk::function::FuncDefOp getConstrainOrProductFuncOp();
155 /// Returns `true` iff this structure defines compute and constrain functions.
156 bool hasComputeConstrain() { return lookupSymbol(FUNC_NAME_COMPUTE) != nullptr && lookupSymbol(FUNC_NAME_CONSTRAIN) != nullptr; }
158 /// Generate header string, in the same format as the assemblyFormat
159 ::std::string getHeaderString();
161 /// Return `false` iff `getConstParamsAttr()` returns `nullptr`
162 bool hasConstParamsAttr() { return getProperties().const_params != nullptr; };
164 /// Return `true` iff this StructDefOp has a parameter with the given name
165 bool hasParamNamed(::mlir::StringAttr find);
166 inline bool hasParamNamed(::mlir::FlatSymbolRefAttr find) {
167 return hasParamNamed(find.getRootReference());
170 //===------------------------------------------------------------------===//
172 //===------------------------------------------------------------------===//
174 /// Return the full name for this struct from the root module, including
175 /// any surrounding module scopes.
176 ::mlir::SymbolRefAttr getFullyQualifiedName();
178 /// Return `true` iff this StructDefOp is the main struct. See `llzk::MAIN_ATTR_NAME`.
179 bool isMainComponent();
182 let hasRegionVerifier = 1;
187 "member", [HasParent<"::llzk::component::StructDefOp">,
188 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
190 let summary = "struct member definition";
192 This operation describes a member in a struct/component.
197 struct.member @f1 : !felt.type
198 struct.member @f2 : !felt.type {llzk.pub}
199 struct.member @col1 : !felt.type {column}
200 struct.member @sig1 : !felt.type {signal}
201 struct.member @colsig1 : !felt.type {column, signal}
202 struct.member @pubcol : !felt.type {llzk.pub, column}
205 - Members marked with the `{llzk.pub}` attribute are considered public
206 and represent outputs of their defining struct/component.
207 - Members marked with the `{column}` attribute can be read/written with
208 table offsets using the `readm` and `writem` operations.
209 - Members marked with the `{signal}` attribute are constraint variables that
210 are stored in the witness; non-signal values are intermediate expressions.
213 let arguments = (ins SymbolNameAttr:$sym_name, TypeAttrOf<AnyLLZKType>:$type,
214 UnitAttr:$column, UnitAttr:$signal);
216 // Define builders manually to avoid the default ones that have extra
217 // TypeRange parameters that must always be empty.
218 let skipDefaultBuilders = 1;
220 [OpBuilder<(ins "::mlir::StringAttr":$sym_name, "::mlir::TypeAttr":$type,
221 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
222 OpBuilder<(ins "::llvm::StringRef":$sym_name, "::mlir::Type":$type,
223 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
224 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
225 "::mlir::ValueRange":$operands,
226 "::llvm::ArrayRef<::mlir::NamedAttribute>":$attributes,
227 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
228 // Simpler version since 'resultTypes' and 'operands' must be empty
230 (ins "::llvm::ArrayRef<::mlir::NamedAttribute>":$attributes,
231 CArg<"bool", "false">:$isSignal,
232 CArg<"bool", "false">:$isColumn),
233 [{ build($_builder, $_state, {}, {}, attributes, isSignal, isColumn); }]>];
235 let assemblyFormat = [{ $sym_name `:` $type attr-dict }];
237 let extraClassDeclaration = [{
238 inline bool hasPublicAttr() { return getOperation()->hasAttr(llzk::PublicAttr::name); }
239 void setPublicAttr(bool newValue = true);
243class MemberRefOpBase<string mnemonic, list<Trait> traits = []>
245 mnemonic, traits#[DeclareOpInterfaceMethods<MemberRefOpInterface>,
246 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
247 bit isRead = ?; // read(1) vs write(0) ops
248 let extraClassDeclaration = [{
249 /// Gets the definition for the `member` referenced in this op.
250 inline ::mlir::FailureOr<SymbolLookupResult<MemberDefOp>> getMemberDefOp(::mlir::SymbolTableCollection &tables) {
251 return ::llvm::cast<MemberRefOpInterface>(getOperation()).getMemberDefOp(tables);
254 let extraClassDefinition = [{
255 /// Return `true` if the op is a read, `false` if it's a write.
256 bool $cppClass::isRead() {
257 return }]#!if(isRead, "true", "false")#[{;
263 : MemberRefOpBase<"readm", [VerifySizesForMultiAffineOps<1>]> {
264 let summary = "read value of a struct member";
266 This operation reads the value of a named member in a struct/component.
268 A struct can read its own members regardless of whether they are marked as
269 public (i.e., with the `llzk.pub` attribute) or private (members without the
270 `llzk.pub` attribute). However, when reading members of other components, only
271 public members can be accessed. Free functions may also only read public members.
273 The value can be read from the signals table, in which case it can be
274 offset by a constant value. A negative value represents reading a value
275 backwards and a positive value represents reading a value forward.
276 Only members marked as columns can be read in this manner.
280 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
281 let arguments = (ins LLZK_StructType:$component,
282 FlatSymbolRefAttr:$member_name,
283 OptionalAttr<AnyAttrOf<[SymbolRefAttr, IndexAttr,
284 AffineMapAttr]>>:$tableOffset,
285 // List of AffineMap operand groups where each group provides the
286 // arguments to instantiate the next (left-to-right) AffineMap used in
288 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
289 // Within each group in '$mapOperands', denotes the number of values that
290 // are AffineMap "dimensional" arguments with the remaining values being
291 // AffineMap "symbolic" arguments.
292 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
293 // Denotes the size of each variadic group in '$mapOperands'.
294 DenseI32ArrayAttr:$mapOpGroupSizes);
295 let results = (outs AnyLLZKType:$val);
297 // Define builders manually so inference of operand layout attributes is not
299 let skipDefaultBuilders = 1;
301 [OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
302 "::mlir::StringAttr":$member)>,
303 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
304 "::mlir::StringAttr":$member, "::mlir::Attribute":$dist,
305 "::mlir::ValueRange":$mapOperands,
306 "std::optional<int32_t>":$numDims)>,
307 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
308 "::mlir::StringAttr":$member,
309 "::mlir::SymbolRefAttr":$dist),
311 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
313 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
314 "::mlir::StringAttr":$member, "::mlir::IntegerAttr":$dist),
316 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
318 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
319 "::mlir::ValueRange":$operands,
320 "::mlir::ArrayRef<::mlir::NamedAttribute>":$attrs)>];
322 let assemblyFormat = [{
323 $component `[` $member_name `]`
324 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
325 `:` type($component) `,` type($val)
332def LLZK_MemberWriteOp : MemberRefOpBase<"writem", [WitnessGen]> {
333 let summary = "write value to a struct member";
335 This operation writes a value to a named member in a struct/component.
337 A struct can write its own members. However, writing members of other components
342 let arguments = (ins LLZK_StructType:$component,
343 FlatSymbolRefAttr:$member_name, AnyLLZKType:$val);
345 let assemblyFormat = [{
346 $component `[` $member_name `]` `=` $val `:` type($component) `,` type($val) attr-dict
350def LLZK_CreateStructOp
351 : StructDialectOp<"new", [DeclareOpInterfaceMethods<
352 OpAsmOpInterface, ["getAsmResultNames"]>,
353 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
356 let summary = "create a new struct";
358 This operation creates a new, uninitialized instance of a struct.
363 %self = struct.new : !struct.type<@Reg>
367 let results = (outs LLZK_StructType:$result);
369 let assemblyFormat = [{ `:` type($result) attr-dict }];
372#endif // LLZK_STRUCT_OPS