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 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9
10#ifndef LLZK_POD_OPS
11#define LLZK_POD_OPS
12
13include "llzk/Dialect/POD/IR/Dialect.td"
14include "llzk/Dialect/POD/IR/OpInterfaces.td"
15include "llzk/Dialect/POD/IR/Types.td"
16include "llzk/Dialect/Shared/OpTraits.td"
17include "llzk/Dialect/Shared/DiscardableAllocationOpInterfaces.td"
18
19include "mlir/IR/OpBase.td"
20include "mlir/IR/OpAsmInterface.td"
21include "mlir/IR/SymbolInterfaces.td"
22include "mlir/Interfaces/SideEffectInterfaces.td"
23
24class PODDialectOp<string mnemonic, list<Trait> traits = []>
25 : Op<PODDialect, mnemonic, traits>;
26
27class PODAccessOpBase<string mnemonic, list<Trait> traits = []>
28 : PODDialectOp<mnemonic,
29 traits#[DeclareOpInterfaceMethods<PodAccessOpInterface>]> {
30 let extraClassDeclaration = [{
31 /// Gets the type of the referenced pod.
32 inline ::llzk::pod::PodType getPodRefType() {
33 return ::llvm::cast<PodAccessOpInterface>(getOperation()).getPodRefType();
34 }
35 }];
36}
37
38// isRead: read(1) vs write(0) ops
39class ScalarPODAccessOp<string mnemonic, bit isRead, list<Trait> traits = []>
40 : PODAccessOpBase<
41 mnemonic,
42 traits#[DeclareOpInterfaceMethods<DestructurableAccessorOpInterface>,
43 DeclareOpInterfaceMethods<PromotableMemOpInterface>,
44 DeclareOpInterfaceMethods<
45 DiscardableAllocationAccessorOpInterface>]> {
46 let extraClassDefinition = [{
47 /// Required by DestructurableAllocationOpInterface / SROA pass
48 bool $cppClass::canRewire(const ::mlir::DestructurableMemorySlot &slot,
49 ::llvm::SmallPtrSetImpl<::mlir::Attribute> &usedIndices,
50 ::mlir::SmallVectorImpl<::mlir::MemorySlot> &mustBeSafelyUsed,
51 const ::mlir::DataLayout &dataLayout) {
52 return ::llvm::cast<PodAccessOpInterface>(getOperation())
53 .canRewire(slot, usedIndices, mustBeSafelyUsed, dataLayout);
54 }
55
56 /// Required by DestructurableAllocationOpInterface / SROA pass
57 ::mlir::DeletionKind $cppClass::rewire(const ::mlir::DestructurableMemorySlot &slot,
58 ::llvm::DenseMap<::mlir::Attribute, ::mlir::MemorySlot> &subslots,
59 ::mlir::OpBuilder &builder, const ::mlir::DataLayout &dataLayout) {
60 return ::llvm::cast<PodAccessOpInterface>(getOperation())
61 .rewire(slot, subslots, builder, dataLayout);
62 }
63
64 /// Required by PromotableMemOpInterface / mem2reg pass
65 bool $cppClass::loadsFrom(const ::mlir::MemorySlot &slot) {
66 return }]#!if(isRead, "getPodRef() == slot.ptr", "false")#[{;
67 }
68
69 /// Required by PromotableMemOpInterface / mem2reg pass
70 bool $cppClass::storesTo(const ::mlir::MemorySlot &slot) {
71 return }]#!if(isRead, "false", "getPodRef() == slot.ptr")#[{;
72 }
73
74 /// Return `true` if the op is a read, `false` if it's a write.
75 bool $cppClass::isRead() {
76 return }]#!if(isRead, "true", "false")#[{;
77 }
78
79 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
80 bool $cppClass::loadsFromDiscardableAllocation(::mlir::Value ptr) {
81 return }]#!if(isRead, "getPodRef() == ptr", "false")#[{;
82 }
83
84 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
85 bool $cppClass::storesToDiscardableAllocation(::mlir::Value ptr) {
86 return }]#!if(isRead, "false",
87 "getPodRef() == ptr && getValue() != ptr")#[{;
88 }
89
90 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
91 bool $cppClass::canEraseAsDeadStoreTo(::mlir::Value ptr, const ::mlir::DataLayout &) {
92 return }]#!if(isRead, "false",
93 "getPodRef() == ptr && getValue() != ptr")#[{;
94 }
95 }];
96}
97
98def LLZK_NewPodOp
99 : PODDialectOp<
100 "new", [MemoryEffects<[MemAlloc<DiscardableAllocationResource>]>,
101 AttrSizedOperandSegments, VerifySizesForMultiAffineOps<1>,
102 DeclareOpInterfaceMethods<PromotableAllocationOpInterface>,
103 DeclareOpInterfaceMethods<
104 DestructurableAllocationOpInterface>,
105 DeclareOpInterfaceMethods<
106 OpAsmOpInterface, ["getAsmResultNames"]>]> {
107 let summary = "create a new plain-old-data struct";
108 let description = [{
109 Creates a new, uninitialized, pod instance. Optionally, the user can pass a list of record names and values
110 that initialize the records of the pod. Partial initialization is allowed. All records without
111 an explicit initialization are initialized with nondeterministic values.
112
113 If the types of the pod records have affine map parameters the user can pass values for them similar
114 to how `array.new` does it.
115
116 This operation returns one value of type `PODType` and, if present, the records passed for initialization
117 must form a subset of the records in the type.
118
119 Examples:
120 ```llzk
121 // Empty pod instance
122 %0 = pod.new : !pod.type<[]>
123
124 // Uninitialized/nondeterministic pod instance
125 %0 = pod.new : !pod.type<[@n: !felt.type]>
126
127 // Initialized pod instance
128 %0 = felt.const 1
129 %1 = pod.new { @n = %0 } : !pod.type<[@n: !felt.type]>
130
131 // Another one, but with 2 fields
132 %0 = felt.const 1
133 %1 = felt.inv %0
134 %2 = pod.new { @n = %0, @inv = %1 } : !pod.type<[@n: !felt.type, @inv: !felt.type]>
135
136 // Partial init
137 %0 = felt.const 1
138 %1 = pod.new { @n = %0 } : !pod.type<[@n: !felt.type, @inv: !felt.type]>
139
140 // Affine map args on uninitialized pod instance
141 %0 = arith.constant 1 : index
142 %c = arith.constant 2 : index
143 %1 = pod.new(%0)[%c] : !pod.type<[@a: !array.type<#map, !felt.type>]>
144
145 // Affine map with initialized records
146 %0 = arith.constant 1 : index
147 %c = arith.constant 2 : index
148 %1 = felt.const 5
149 %2 = pod.new { @f = %1 }(%0)[%c] : !pod.type<[@f: !felt.type, @a: !array.type<#map, !felt.type>]>
150 ```
151 }];
152
153 let arguments = (ins
154 // Initialization values
155 Variadic<AnyLLZKType>:$initialValues,
156 DefaultValuedAttr<StrArrayAttr, "{}">:$initializedRecords,
157 // Affine map arguments
158 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
159 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
160 DenseI32ArrayAttr:$mapOpGroupSizes);
161 let results = (outs LLZK_PODType:$result);
162 let skipDefaultBuilders = 1;
163 let builders =
164 [OpBuilder<
165 (ins CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues),
166 [{
167 auto resultType = ::llzk::pod::PodType::fromInitialValues($_builder.getContext(), initialValues);
168 build($_builder, $_state, resultType, initialValues);
169 }]>,
170 OpBuilder<(ins "::llzk::pod::PodType":$resultType,
171 CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues)>,
172 OpBuilder<(ins "::llzk::pod::PodType":$resultType,
173 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
174 "::mlir::DenseI32ArrayAttr":$numDimsPerMap,
175 CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues)>,
176 OpBuilder<
177 (ins "::llzk::pod::PodType":$resultType,
178 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
179 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
180 CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues),
181 [{
182 build($_builder, $_state, resultType, mapOperands,
183 $_builder.getDenseI32ArrayAttr(numDimsPerMap), initialValues);
184 }]>];
185 let hasCustomAssemblyFormat = 1;
186 let hasVerifier = 1;
187
188 let extraClassDeclaration = [{
189 ::mlir::SmallVector<::llzk::pod::RecordValue> getInitializedRecordValues();
190 }];
191}
192
193def LLZK_ReadPodOp : ScalarPODAccessOp<"read", 1> {
194 let summary = "reads the contents of a plain-old-data struct record";
195 let description = [{
196 Copies the current value of a named record from a pod instance. The result is a
197 value copy, not a pointer or alias to the record's storage. Returns one value of
198 the type of the record. For an identity-bearing `struct.type` record, the copied
199 value is a handle to the same component instance; it does not duplicate the
200 component's members, signals, or tables. Immutable scalars and shape-only arrays
201 may likewise reuse their SSA representation because they have no independently
202 mutable payload. Lowering must reject a mutable aggregate when it cannot
203 materialize independent storage for the copy.
204
205 The name of the record must be a valid record name for the pod type and the result
206 type must match the type of the record.
207
208 Example:
209 ```llzk
210 %1 = pod.read %0[@sym] : !pod.type<[@sym: !type, ...]>, !type
211 ```
212 }];
213
214 let arguments = (ins Arg<LLZK_PODType, "the pod to read from">:$pod_ref,
215 StrAttr:$record_name);
216 let results = (outs AnyLLZKType:$result);
217 let assemblyFormat = [{
218 $pod_ref `[` custom<RecordName>($record_name) `]` `:` type($pod_ref) `,` type($result) attr-dict
219 }];
220 let useCustomPropertiesEncoding = 1;
221 let hasVerifier = 1;
222}
223
224def LLZK_WritePodOp : ScalarPODAccessOp<"write", 0> {
225 let summary = "writes content into a plain-old-data struct record";
226 let description = [{
227 Copies a value into a named record of a pod instance. It does not create a pointer
228 or alias between the source value and the record's storage. For an identity-bearing
229 `struct.type` source, the stored value is a handle to the same component instance;
230 it does not duplicate the component's members, signals, or tables. This operation
231 has no result values. Immutable scalars and shape-only arrays may reuse their SSA
232 representation because they have no independently mutable payload. Lowering must
233 reject a mutable aggregate when it cannot materialize independent storage for the
234 copy.
235
236 The name of the record must be a valid record name for the pod type and the source
237 value type must match the type of the record.
238
239 Writing a record that was written or initialized earlier overwrites the previous value.
240
241 Example:
242 ```llzk
243 pod.write %0[@sym] = %1 : !pod.type<[@sym: !type, ...]>, !type
244 ```
245 }];
246
247 let arguments = (ins Arg<LLZK_PODType, "the pod to write into">:$pod_ref,
248 StrAttr:$record_name, AnyLLZKType:$value);
249
250 let assemblyFormat = [{
251 $pod_ref `[` custom<RecordName>($record_name) `]` `=` $value `:` type($pod_ref) `,` type($value) attr-dict
252 }];
253 let useCustomPropertiesEncoding = 1;
254 let hasVerifier = 1;
255}
256
257#endif // LLZK_POD_OPS