LLZK 3.0.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// Copyright 2026 Project LLZK
7// SPDX-License-Identifier: Apache-2.0
8//
9// Adapted from mlir/include/mlir/Dialect/Func/IR/FuncOps.td
10// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
11// See https://llvm.org/LICENSE.txt for license information.
12// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
13//
14//===----------------------------------------------------------------------===//
15
16#ifndef LLZK_STRUCT_OPS
17#define LLZK_STRUCT_OPS
18
19include "llzk/Dialect/Function/IR/OpTraits.td"
20include "llzk/Dialect/Struct/IR/Dialect.td"
21include "llzk/Dialect/Struct/IR/OpInterfaces.td"
22include "llzk/Dialect/Struct/IR/Types.td"
23include "llzk/Dialect/Shared/OpTraits.td"
24include "llzk/Dialect/Verif/IR/OpInterfaces.td"
25
26include "mlir/IR/OpAsmInterface.td"
27include "mlir/IR/RegionKindInterface.td"
28include "mlir/IR/SymbolInterfaces.td"
29include "mlir/Interfaces/SideEffectInterfaces.td"
30
31class StructDialectOp<string mnemonic, list<Trait> traits = []>
32 : Op<StructDialect, mnemonic, traits>;
33
34/// Only valid/implemented for StructDefOp. Sets the proper `AllowWitnessAttr`
35/// and `AllowConstraintAttr` on the functions defined within the StructDefOp.
36def SetFuncAllowAttrs : NativeOpTrait<"SetFuncAllowAttrs">, StructuralOpTrait {
37 string cppNamespace = "::llzk::component";
38}
39
40//===------------------------------------------------------------------===//
41// Struct Operations
42//===------------------------------------------------------------------===//
43
44def LLZK_StructDefOp
45 : StructDialectOp<"def", [ParentOneOf<["::mlir::ModuleOp",
46 "::llzk::polymorphic::TemplateOp"]>,
47 Symbol, LLZKSymbolTable, IsolatedFromAbove,
48 SetFuncAllowAttrs, NoRegionArguments,
49 ContractTarget]#GraphRegionNoTerminator.traits> {
50 let summary = "circuit component definition";
51 let description = [{
52 This operation describes a component in a circuit. It can contain any number
53 of members that hold inputs, outputs, intermediate values, and subcomponents
54 of the defined component. It also contains a `compute()` function that holds
55 the witness generation code for the component and a `constrain()` function
56 that holds that constraint generation code for the component.
57
58 Example:
59
60 ```llzk
61 struct.def @ComponentA {
62 member @f1 : !array.type<5 x index>
63 member @f2 : !felt.type {llzk.pub}
64
65 function.def @compute(%p: !felt.type) -> !struct.type<@ComponentA> {
66 %self = struct.new : !struct.type<@ComponentA>
67 // initialize all members of `%self` here
68 return %self : !struct.type<@ComponentA>
69 }
70
71 function.def @constrain(%self: !struct.type<@ComponentA>, %p: !felt.type) {
72 // emit constraints here
73 return
74 }
75 }
76 ```
77
78 The optional `llzk.main = !struct.type<...>` attribute on the top-level module in an
79 LLZK IR program expresses the main entry point of the circuit as a concrete instantiation
80 of a specific struct. For example, `llzk.main = !struct.type<@Top<[52,12]>>` or
81 `llzk.main = !struct.type<@Main<[i1, !felt.type, 256]>>`.
82 This struct has additional restrictions:
83 1. The parameter types of its functions (besides the required "self" parameter) can
84 only be `!felt.type` or `!array.type<.. x !felt.type>`.
85 2. All inputs to the main struct are signals, even though they are not marked with
86 the `{signal}` attribute (as the `{signal}` attribute is not used on function arguments).
87 Input signals may be public (given the `{llzk.pub}` attribute) or private (by default).
88 3. All public members of the main struct are also signals (representing public output signals),
89 and can also only be `!felt.type` or `!array.type<.. x !felt.type>`.
90
91 Example of a `Main` component:
92
93 ```llzk
94 module attributes {llzk.main = !struct.type<@Main>, llzk.lang} {
95 struct.def @Main {
96 member @out : !felt.type {llzk.pub, signal} // public output signal
97 member @out2 : !felt.type {llzk.pub} // also a public output signal, implicitly
98 member @intermediate : !struct.type<@OtherStruct> // an intermediate value, not a signal, can be any LLZK type
99 // %p is a private input signal
100 function.def @compute(%p: !felt.type) -> !struct.type<@Main> {
101 // ...
102 }
103 // %self is not a signal, but again, %p is a private input signal
104 function.def @constrain(%self: !struct.type<@Main>, %p: !felt.type) {
105 // ...
106 }
107 }
108 }
109 ```
110 }];
111
112 let arguments = (ins SymbolNameAttr:$sym_name);
113
114 let regions = (region SizedRegion<1>:$bodyRegion);
115
116 let assemblyFormat = [{ $sym_name $bodyRegion attr-dict }];
117
118 let useCustomPropertiesEncoding = 1;
119
120 let extraClassDeclaration = [{
121 /// Gets the StructType representing this struct. If the `constParams` to use in
122 /// the type are not given, the StructType will use `this->getParams()`.
123 StructType getType(::std::optional<::mlir::ArrayAttr> constParams = {});
124 inline StructType getType(::std::optional<::mlir::ArrayAttr> constParams = {}) const {
125 return const_cast<StructDefOp*>(this)->getType(constParams);
126 }
127
128 /// Gets the MemberDefOp that defines the member in this
129 /// structure with the given name, if present.
130 MemberDefOp getMemberDef(::mlir::StringAttr memberName);
131
132 /// Get all MemberDefOp in this structure.
133 ::std::vector<MemberDefOp> getMemberDefs();
134
135 /// Returns whether the struct defines members marked as columns.
136 ::mlir::LogicalResult hasColumns() {
137 return ::mlir::success(::llvm::any_of(getMemberDefs(), [](MemberDefOp memberOp) {
138 return memberOp.getColumn();
139 }));
140 }
141
142 /// Returns whether the struct defines members marked as signals.
143 ::mlir::LogicalResult hasSignals() {
144 return ::mlir::success(::llvm::any_of(getMemberDefs(), [](MemberDefOp memberOp) {
145 return memberOp.getSignal();
146 }));
147 }
148
149 /// Gets the FuncDefOp that defines the compute function in this structure, if present, or `nullptr` otherwise.
150 ::llzk::function::FuncDefOp getComputeFuncOp();
151
152 /// Gets the FuncDefOp that defines the constrain function in this structure, if present, or `nullptr` otherwise.
153 ::llzk::function::FuncDefOp getConstrainFuncOp();
154
155 /// Gets the FuncDefOp that defines the product function in this structure, if present, or `nullptr` otherwise
156 ::llzk::function::FuncDefOp getProductFuncOp();
157
158 /// Returns `true` iff this structure defines compute and constrain functions.
159 bool hasComputeConstrain() { return lookupSymbol(FUNC_NAME_COMPUTE) != nullptr && lookupSymbol(FUNC_NAME_CONSTRAIN) != nullptr; }
160
161 /// Generate header string, in the same format as the assemblyFormat.
162 ::std::string getHeaderString();
163
164 /// Return `true` iff the `struct.def` appears within a `poly.template` that defines
165 /// constant parameters and/or expressions.
166 bool hasTemplateSymbolBindings();
167
168 /// If this `struct.def` is within a `poly.template`, return names of all `poly.param`
169 /// within the `poly.template` in the order they are defined. Otherwise, return empty.
170 /// The names are returned as `FlatSymbolRefAttr` but the more general `Attribute` type
171 /// is used in the return type since that's usually what's needed.
172 ::llvm::SmallVector<::mlir::Attribute> getTemplateParamOpNames();
173
174 /// If this `struct.def` is within a `poly.template`, return names of all `poly.expr`
175 /// within the `poly.template` in the order they are defined. Otherwise, return empty.
176 /// The names are returned as `FlatSymbolRefAttr` but the more general `Attribute` type
177 /// is used in the return type since that's usually what's needed.
178 ::llvm::SmallVector<::mlir::Attribute> getTemplateExprOpNames();
179
180 /// Return the full name for this struct from the root module, including
181 /// any surrounding module scopes.
182 ::mlir::SymbolRefAttr getFullyQualifiedName();
183
184 /// Return `true` iff this `struct.def` is the main struct. See `llzk::MAIN_ATTR_NAME`.
185 bool isMainComponent();
186 }];
187
188 let hasRegionVerifier = 1;
189}
190
191def LLZK_MemberDefOp
192 : StructDialectOp<
193 "member", [HasParent<"::llzk::component::StructDefOp">,
194 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
195 Symbol]> {
196 let summary = "struct member definition";
197 let description = [{
198 This operation describes a member in a struct/component.
199
200 Example:
201
202 ```llzk
203 struct.member @f1 : !felt.type
204 struct.member @f2 : !felt.type {llzk.pub}
205 struct.member @col1 : !felt.type {column}
206 struct.member @sig1 : !felt.type {signal}
207 struct.member @colsig1 : !felt.type {column, signal}
208 struct.member @pubcol : !felt.type {llzk.pub, column}
209 ```
210
211 - Members marked with the `{llzk.pub}` attribute are considered public
212 and represent outputs of their defining struct/component.
213 - Members marked with the `{column}` attribute can be read/written with
214 table offsets using the `readm` and `writem` operations.
215 - Members marked with the `{signal}` attribute are constraint variables that
216 are stored in the witness; non-signal values are intermediate expressions.
217
218 Further restrictions on the `signal` attribute:
219 - It is only used on struct members, since they represent storage locations.
220 It is not used to mark struct inputs, as struct may be passed signal or non-signal
221 (i.e., intermediate expression) values.
222 - Only !felt.type or simple aggregates of !felt.type (i.e., !array.type and !pod.type)
223 may be marked as signals.
224 }];
225
226 let arguments = (ins SymbolNameAttr:$sym_name, TypeAttrOf<AnyLLZKType>:$type,
227 UnitAttr:$column, UnitAttr:$signal);
228
229 // Define builders manually to avoid the default ones that have extra
230 // TypeRange parameters that must always be empty.
231 let skipDefaultBuilders = 1;
232 let builders =
233 [OpBuilder<(ins "::mlir::StringAttr":$sym_name, "::mlir::TypeAttr":$type,
234 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
235 OpBuilder<(ins "::llvm::StringRef":$sym_name, "::mlir::Type":$type,
236 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
237 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
238 "::mlir::ValueRange":$operands,
239 "::llvm::ArrayRef<::mlir::NamedAttribute>":$attributes,
240 CArg<"bool", "false">:$isSignal, CArg<"bool", "false">:$isColumn)>,
241 // Simpler version since 'resultTypes' and 'operands' must be empty
242 OpBuilder<
243 (ins "::llvm::ArrayRef<::mlir::NamedAttribute>":$attributes,
244 CArg<"bool", "false">:$isSignal,
245 CArg<"bool", "false">:$isColumn),
246 [{ build($_builder, $_state, {}, {}, attributes, isSignal, isColumn); }]>];
247
248 let assemblyFormat = [{ $sym_name `:` $type attr-dict }];
249
250 let extraClassDeclaration = [{
251 /// Returns whether this member is a public output.
252 inline bool hasPublicAttr() { return getOperation()->hasAttr(llzk::PublicAttr::name); }
253
254 /// Adds or removes the unit `llzk.pub` attribute according to `newValue`.
255 void setPublicAttr(bool newValue = true);
256 }];
257
258 let hasVerifier = 1;
259}
260
261class MemberRefOpBase<string mnemonic, list<Trait> traits = []>
262 : StructDialectOp<
263 mnemonic, traits#[DeclareOpInterfaceMethods<MemberRefOpInterface>,
264 DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
265 bit isRead = ?; // read(1) vs write(0) ops
266 let extraClassDeclaration = [{
267 /// Gets the definition for the `member` referenced in this op.
268 inline ::mlir::FailureOr<SymbolLookupResult<MemberDefOp>> getMemberDefOp(::mlir::SymbolTableCollection &tables) {
269 return ::llvm::cast<MemberRefOpInterface>(getOperation()).getMemberDefOp(tables);
270 }
271 }];
272 let extraClassDefinition = [{
273 /// Return `true` if the op is a read, `false` if it's a write.
274 bool $cppClass::isRead() {
275 return }]#!if(isRead, "true", "false")#[{;
276 }
277 }];
278}
279
280def LLZK_MemberReadOp
281 : MemberRefOpBase<"readm", [VerifySizesForMultiAffineOps<1>,
282 MemoryEffects<[MemRead]>]> {
283 let summary = "read value of a struct member";
284 let description = [{
285 This operation reads the value of a named member in a struct/component.
286
287 A struct can read its own members regardless of whether they are marked as
288 public (i.e., with the `llzk.pub` attribute) or private (members without the
289 `llzk.pub` attribute). However, when reading members of other components, only
290 public members can be accessed. Free functions may also only read public members.
291
292 The value can be read from the signals table, in which case it can be
293 offset by a constant value. A negative value represents reading a value
294 backwards and a positive value represents reading a value forward.
295 Only members marked as columns can be read in this manner.
296 }];
297 let isRead = 1;
298
299 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
300 let arguments = (ins LLZK_StructType:$component,
301 FlatSymbolRefAttr:$member_name,
302 OptionalAttr<AnyAttrOf<[SymbolRefAttr, IndexAttr,
303 AffineMapAttr]>>:$tableOffset,
304 // List of AffineMap operand groups where each group provides the
305 // arguments to instantiate the next (left-to-right) AffineMap used in
306 // `tableOffset`.
307 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
308 // Within each group in '$mapOperands', denotes the number of values that
309 // are AffineMap "dimensional" arguments with the remaining values being
310 // AffineMap "symbolic" arguments.
311 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
312 // Denotes the size of each variadic group in '$mapOperands'.
313 DenseI32ArrayAttr:$mapOpGroupSizes);
314 let results = (outs AnyLLZKType:$val);
315
316 // Define builders manually so inference of operand layout attributes is not
317 // circumvented.
318 let skipDefaultBuilders = 1;
319 let builders =
320 [OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
321 "::mlir::StringAttr":$member)>,
322 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
323 "::mlir::StringAttr":$member, "::mlir::Attribute":$dist,
324 "::mlir::ValueRange":$mapOperands,
325 "std::optional<int32_t>":$numDims)>,
326 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
327 "::mlir::StringAttr":$member,
328 "::mlir::SymbolRefAttr":$dist),
329 [{
330 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
331 }]>,
332 OpBuilder<(ins "::mlir::Type":$resultType, "::mlir::Value":$component,
333 "::mlir::StringAttr":$member, "::mlir::IntegerAttr":$dist),
334 [{
335 build($_builder, $_state, resultType, component, member, dist, ::mlir::ValueRange(), std::nullopt);
336 }]>,
337 OpBuilder<(ins "::mlir::TypeRange":$resultTypes,
338 "::mlir::ValueRange":$operands,
339 "::mlir::ArrayRef<::mlir::NamedAttribute>":$attrs)>];
340
341 let assemblyFormat = [{
342 $component `[` $member_name `]`
343 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` )?
344 `:` type($component) `,` type($val)
345 attr-dict
346 }];
347
348 let hasVerifier = 1;
349}
350
351def LLZK_MemberWriteOp
352 : MemberRefOpBase<"writem", [WitnessGen, MemoryEffects<[MemWrite]>]> {
353 let summary = "write value to a struct member";
354 let description = [{
355 This operation writes a value to a named member in a struct/component.
356
357 A struct can write its own members. However, writing members of other components
358 is not allowed.
359 }];
360 let isRead = 0;
361
362 let arguments = (ins LLZK_StructType:$component,
363 FlatSymbolRefAttr:$member_name, AnyLLZKType:$val);
364
365 let assemblyFormat = [{
366 $component `[` $member_name `]` `=` $val `:` type($component) `,` type($val) attr-dict
367 }];
368}
369
370def LLZK_CreateStructOp
371 : StructDialectOp<"new", [DeclareOpInterfaceMethods<
372 OpAsmOpInterface, ["getAsmResultNames"]>,
373 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
374 WitnessGen,
375]> {
376 let summary = "create a new struct";
377 let description = [{
378 This operation creates a new, uninitialized instance of a struct.
379
380 Example:
381
382 ```llzk
383 %self = struct.new : !struct.type<@Reg>
384 ```
385 }];
386
387 let results = (outs LLZK_StructType:$result);
388
389 let assemblyFormat = [{ `:` type($result) attr-dict }];
390}
391
392#endif // LLZK_STRUCT_OPS