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