LLZK 0.1.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
Ops.td
Go to the documentation of this file.
1//===-- Ops.td ---------------------------------------------*- tablegen -*-===//
2//
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
7//
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
12//
13//===----------------------------------------------------------------------===//
14
15#ifndef LLZK_STRUCT_OPS
16#define LLZK_STRUCT_OPS
17
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"
23
24include "mlir/IR/OpAsmInterface.td"
25include "mlir/IR/RegionKindInterface.td"
26include "mlir/IR/SymbolInterfaces.td"
27
28class StructDialectOp<string mnemonic, list<Trait> traits = []>
29 : Op<StructDialect, mnemonic, traits>;
30
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";
35}
36
37//===------------------------------------------------------------------===//
38// Struct Operations
39//===------------------------------------------------------------------===//
40
41def LLZK_StructDefOp
42 : StructDialectOp<
43 "def", [HasParent<"::mlir::ModuleOp">, Symbol, SymbolTable,
44 IsolatedFromAbove, SetFuncAllowAttrs,
45 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
46 NoRegionArguments]#GraphRegionNoTerminator.traits> {
47 let summary = "circuit component definition";
48 let description = [{
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.
54
55 Example:
56
57 ```llzk
58 struct.def @ComponentA {
59 member @f1 : !array.type<5 x index>
60 member @f2 : !felt.type {llzk.pub}
61
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>
66 }
67
68 function.def @constrain(%self: !struct.type<@ComponentA>, %p: !felt.type) {
69 // emit constraints here
70 return
71 }
72 }
73 ```
74
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.
84
85 Example of a `Main` component:
86
87 ```llzk
88 struct.def @Main {
89 member @out : !felt.type {llzk.pub} // public output
90 member @intermediate : !felt.type
91 function.def @compute(%p: !felt.type) -> !struct.type<@Main> {
92 // ...
93 }
94 function.def @constrain(%self: !struct.type<@Main>, %p: !felt.type) {
95 // ...
96 }
97 }
98 ```
99 }];
100
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);
106
107 let regions = (region SizedRegion<1>:$bodyRegion);
108
109 let assemblyFormat = [{
110 $sym_name (`<` $const_params^ `>`)? $bodyRegion attr-dict
111 }];
112
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);
119 }
120
121 /// Gets the MemberDefOp that defines the member in this
122 /// structure with the given name, if present.
123 MemberDefOp getMemberDef(::mlir::StringAttr memberName);
124
125 /// Get all MemberDefOp in this structure.
126 ::std::vector<MemberDefOp> getMemberDefs();
127
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();
132 }));
133 }
134
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();
139 }));
140 }
141
142
143 /// Gets the FuncDefOp that defines the compute function in this structure, if present, or `nullptr` otherwise.
144 ::llzk::function::FuncDefOp getComputeFuncOp();
145
146 /// Gets the FuncDefOp that defines the constrain function in this structure, if present, or `nullptr` otherwise.
147 ::llzk::function::FuncDefOp getConstrainFuncOp();
148
149 /// Gets the FuncDefOp that defines the compute function in this structure, if present, or the product function otherwise.
150 ::llzk::function::FuncDefOp getComputeOrProductFuncOp();
151
152 /// Gets the FuncDefOp that defines the constrain function in this structure, if present, or the product function otherwise.
153 ::llzk::function::FuncDefOp getConstrainOrProductFuncOp();
154
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; }
157
158 /// Generate header string, in the same format as the assemblyFormat
159 ::std::string getHeaderString();
160
161 /// Return `false` iff `getConstParamsAttr()` returns `nullptr`
162 bool hasConstParamsAttr() { return getProperties().const_params != nullptr; };
163
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());
168 }
169
170 //===------------------------------------------------------------------===//
171 // Utility Methods
172 //===------------------------------------------------------------------===//
173
174 /// Return the full name for this struct from the root module, including
175 /// any surrounding module scopes.
176 ::mlir::SymbolRefAttr getFullyQualifiedName();
177
178 /// Return `true` iff this StructDefOp is the main struct. See `llzk::MAIN_ATTR_NAME`.
179 bool isMainComponent();
180 }];
181
182 let hasRegionVerifier = 1;
183}
184
185def LLZK_MemberDefOp
186 : StructDialectOp<
187 "member", [HasParent<"::llzk::component::StructDefOp">,
188 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
189 Symbol]> {
190 let summary = "struct member definition";
191 let description = [{
192 This operation describes a member in a struct/component.
193
194 Example:
195
196 ```llzk
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}
203 ```
204
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.
211 }];
212
213 let arguments = (ins SymbolNameAttr:$sym_name, TypeAttrOf<AnyLLZKType>:$type,
214 UnitAttr:$column, UnitAttr:$signal);
215
216 // Define builders manually to avoid the default ones that have extra
217 // TypeRange parameters that must always be empty.
218 let skipDefaultBuilders = 1;
219 let builders =
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
229 OpBuilder<
230 (ins "::llvm::ArrayRef<::mlir::NamedAttribute>":$attributes,
231 CArg<"bool", "false">:$isSignal,
232 CArg<"bool", "false">:$isColumn),
233 [{ build($_builder, $_state, {}, {}, attributes, isSignal, isColumn); }]>];
234
235 let assemblyFormat = [{ $sym_name `:` $type attr-dict }];
236
237 let extraClassDeclaration = [{
238 inline bool hasPublicAttr() { return getOperation()->hasAttr(llzk::PublicAttr::name); }
239 void setPublicAttr(bool newValue = true);
240 }];
241}
242
243class MemberRefOpBase<string mnemonic, list<Trait> traits = []>
244 : StructDialectOp<
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);
252 }
253 }];
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")#[{;
258 }
259 }];
260}
261
262def LLZK_MemberReadOp
263 : MemberRefOpBase<"readm", [VerifySizesForMultiAffineOps<1>]> {
264 let summary = "read value of a struct member";
265 let description = [{
266 This operation reads the value of a named member in a struct/component.
267
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.
272
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.
277 }];
278 let isRead = 1;
279
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
287 // `tableOffset`.
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);
296
297 // Define builders manually so inference of operand layout attributes is not
298 // circumvented.
299 let skipDefaultBuilders = 1;
300 let builders =
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),
310 [{
311 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
312 }]>,
313 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
314 "::mlir::StringAttr":$member, "::mlir::IntegerAttr":$dist),
315 [{
316 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
317 }]>,
318 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
319 "::mlir::ValueRange":$operands,
320 "::mlir::ArrayRef<::mlir::NamedAttribute>":$attrs)>];
321
322 let assemblyFormat = [{
323 $component `[` $member_name `]`
324 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
325 `:` type($component) `,` type($val)
326 attr-dict
327 }];
328
329 let hasVerifier = 1;
330}
331
332def LLZK_MemberWriteOp : MemberRefOpBase<"writem", [WitnessGen]> {
333 let summary = "write value to a struct member";
334 let description = [{
335 This operation writes a value to a named member in a struct/component.
336
337 A struct can write its own members. However, writing members of other components
338 is not allowed.
339 }];
340 let isRead = 0;
341
342 let arguments = (ins LLZK_StructType:$component,
343 FlatSymbolRefAttr:$member_name, AnyLLZKType:$val);
344
345 let assemblyFormat = [{
346 $component `[` $member_name `]` `=` $val `:` type($component) `,` type($val) attr-dict
347 }];
348}
349
350def LLZK_CreateStructOp
351 : StructDialectOp<"new", [DeclareOpInterfaceMethods<
352 OpAsmOpInterface, ["getAsmResultNames"]>,
353 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
354 WitnessGen,
355]> {
356 let summary = "create a new struct";
357 let description = [{
358 This operation creates a new, uninitialized instance of a struct.
359
360 Example:
361
362 ```llzk
363 %self = struct.new : !struct.type<@Reg>
364 ```
365 }];
366
367 let results = (outs LLZK_StructType:$result);
368
369 let assemblyFormat = [{ `:` type($result) attr-dict }];
370}
371
372#endif // LLZK_STRUCT_OPS