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//===----------------------------------------------------------------------===//
13include "llzk/Dialect/Array/IR/Dialect.td"
14include "llzk/Dialect/Array/IR/OpInterfaces.td"
15include "llzk/Dialect/Array/IR/Types.td"
16include "llzk/Dialect/Shared/DiscardableAllocationOpInterfaces.td"
17include "llzk/Dialect/Shared/OpTraits.td"
19include "mlir/IR/OpAsmInterface.td"
20include "mlir/IR/OpBase.td"
21include "mlir/IR/SymbolInterfaces.td"
22include "mlir/Interfaces/SideEffectInterfaces.td"
24class ArrayDialectOp<string mnemonic, list<Trait> traits = []>
25 : Op<ArrayDialect, mnemonic, traits>;
27class ArrayAccessOpBase<string mnemonic, list<Trait> traits = []>
30 traits#[DeclareOpInterfaceMethods<SymbolUserOpInterface>,
31 DeclareOpInterfaceMethods<ArrayAccessOpInterface>]> {
32 let extraClassDeclaration = [{
33 /// Gets the type of the referenced base array.
34 inline ::llzk::array::ArrayType getArrRefType() {
35 return ::llvm::cast<ArrayAccessOpInterface>(getOperation()).getArrRefType();
40// isRead: read(1) vs write(0) ops
41class ScalarArrayAccessOp<string mnemonic, bit isRead, list<Trait> traits = []>
44 traits#[DeclareOpInterfaceMethods<DestructurableAccessorOpInterface>,
45 DeclareOpInterfaceMethods<PromotableMemOpInterface>,
46 DeclareOpInterfaceMethods<
47 DiscardableAllocationAccessorOpInterface>]> {
48 let extraClassDefinition = [{
49 /// Required by DestructurableAllocationOpInterface / SROA pass
50 bool $cppClass::canRewire(const ::mlir::DestructurableMemorySlot &slot,
51 ::llvm::SmallPtrSetImpl<::mlir::Attribute> &usedIndices,
52 ::mlir::SmallVectorImpl<::mlir::MemorySlot> &mustBeSafelyUsed,
53 const ::mlir::DataLayout &dataLayout) {
54 return ::llvm::cast<ArrayAccessOpInterface>(getOperation())
55 .canRewire(slot, usedIndices, mustBeSafelyUsed, dataLayout);
58 /// Required by DestructurableAllocationOpInterface / SROA pass
59 ::mlir::DeletionKind $cppClass::rewire(const ::mlir::DestructurableMemorySlot &slot,
60 ::llvm::DenseMap<::mlir::Attribute, ::mlir::MemorySlot> &subslots,
61 ::mlir::OpBuilder &builder, const ::mlir::DataLayout &dataLayout) {
62 return ::llvm::cast<ArrayAccessOpInterface>(getOperation())
63 .rewire(slot, subslots, builder, dataLayout);
66 /// Required by PromotableMemOpInterface / mem2reg pass
67 bool $cppClass::loadsFrom(const ::mlir::MemorySlot &slot) {
68 return }]#!if(isRead, "getArrRef() == slot.ptr", "false")#[{;
71 /// Required by PromotableMemOpInterface / mem2reg pass
72 bool $cppClass::storesTo(const ::mlir::MemorySlot &slot) {
73 return }]#!if(isRead, "false", "getArrRef() == slot.ptr")#[{;
76 /// Required by PromotableAllocationOpInterface / mem2reg pass
77 ::mlir::Value $cppClass::getStored(const ::mlir::MemorySlot &, ::mlir::OpBuilder &,
78 ::mlir::Value, const ::mlir::DataLayout &) {
80 "llvm_unreachable(\"getStored() should not be called on "
82 "return getRvalue()")#[{;
85 /// Return `true` if the op is a read, `false` if it's a write.
86 bool $cppClass::isRead() {
87 return }]#!if(isRead, "true", "false")#[{;
90 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
91 bool $cppClass::loadsFromDiscardableAllocation(::mlir::Value ptr) {
92 return }]#!if(isRead, "getArrRef() == ptr", "false")#[{;
95 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
96 bool $cppClass::storesToDiscardableAllocation(::mlir::Value ptr) {
97 return }]#!if(isRead, "false",
98 "getArrRef() == ptr && getRvalue() != ptr")#[{;
101 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
102 bool $cppClass::canEraseAsDeadStoreTo(::mlir::Value ptr, const ::mlir::DataLayout &) {
103 return }]#!if(isRead, "false",
104 "getArrRef() == ptr && getRvalue() != ptr")#[{;
109// isRead: read(1) vs write(0) ops
110class RangedArrayAccessOp<string mnemonic, bit isRead, list<Trait> traits = []>
111 : ArrayAccessOpBase<mnemonic, traits> {
112 let extraClassDefinition = [{
113 /// Return `true` if the op is a read, `false` if it's a write.
114 bool $cppClass::isRead() {
115 return }]#!if(isRead, "true", "false")#[{;
120//===------------------------------------------------------------------===//
122//===------------------------------------------------------------------===//
124def LLZK_CreateArrayOp
127 [MemoryEffects<[MemAlloc<DiscardableAllocationResource>]>,
128 AttrSizedOperandSegments, VerifySizesForMultiAffineOps<1>,
129 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
130 DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
131 DeclareOpInterfaceMethods<PromotableAllocationOpInterface>,
132 DeclareOpInterfaceMethods<DestructurableAllocationOpInterface>,
133 VariadicTypesMatchWith<
134 "operand types match result type", "result", "elements",
135 "resultTypeToElementsTypes($_self)", "std::equal_to<>()">]> {
136 let summary = "create a new array";
138 This operation creates a fresh mutable array with the given elements.
139 Even when two `array.new` operations have identical operands and result
140 types, they are distinct allocations; writes through one result must not
141 affect reads through the other.
143 The arguments are passed as a flat array but get arranged in
144 row-major order according the shape declared in the type.
148 %0 = array.new %a, %b, %c : !array.type<3 x !felt.type>
150 // Create an array of the specified shape, initialized with the given
151 // values assigned in row-major order: [[%a, %b], [%c, %d]]
152 %1 = array.new %a, %b, %c, %d : !array.type<2,2 x !felt.type>
154 // Create an uninitialized array: [[?, ?], [?, ?], [?, ?]]
155 %2 = array.new : !array.type<3,2 x !felt.type>
158 The values used to construct the array must have type that exactly matches
159 the element type of the specified array type. This is true even if a `tvar`
160 type is used. In other words, cannot mix `tvar<@X>` with `tvar<@Y>` or any
161 concrete type. In such a scenario, first create an uninitialized array, as
162 shown in the examples above, and then use `array.write` to write each element
165 Implementation note: This restriction exists due to a combination of:
166 (1) we have chosen to infer the type of `$elements` from the `$result`
167 ArrayType, via parseInferredArrayType(), rather than requiring the type of
168 every element be listed in the assembly format and,
169 (2) within the parser for an Op, there is no way to get the Value instances
170 for the operands aside from `OpAsmParser::resolveOperands()` which requires
171 the type of every operand to be known and ends up comparing the expected
172 to actual type via `operator==`. Thus, there is no way for this to be
173 successful apart from all elements having the exact type inferred in (1).
175 Also note that `std::equal_to` is used in the `VariadicTypesMatchWith`
176 trait on this Op so that the verifier function mirrors the aforementioned
177 restriction in the parser.
179 In some cases, the length of an uninitialized array depends on the value
180 of the loop induction variable (i.e., each iteration creates an array with
181 a different size/shape). In that case, an AffineMapAttr can be used to
182 specify the dimension size in the ArrayType and the optional instantiation
183 parameter list of this operation must be used to instatiate all AffineMap
184 used in the array dimensions.
188 // Create an uninitialized array with dimension size defined by AffineMap
189 #IdxToLen = affine_map<(i) -> (5*i+1)>
190 %3 = array.new {(%i)} : !array.type<#IdxToLen x index>
192 // Create an uninitialized array with multiple dimensions defined by
193 // AffineMap. The list of instantiation parameters are assigned to
194 // the AffineMap dimensions left-to-right.
195 #M1 = affine_map<(i)[c] -> (c+i)>
196 #M3 = affine_map<(m,n) -> (5*m+n)>
197 %4 = array.new{(%i)[%c], (%m,%n)} : !array.type<#M1,2,#M3 x i1>
201 // See `VerifySizesForMultiAffineOps` for more explanation of these arguments.
203 // Elements to initialize the array. Length is either zero or equal to
204 // the length of the flattened/linearized result ArrayType.
205 Variadic<ArrayElemType>:$elements,
206 // List of AffineMap operand groups where each group provides the
207 // arguments to instantiate the next (left-to-right) AffineMap used as an
208 // array dimension in the result ArrayType.
209 VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands,
210 // Within each group in '$mapOperands', denotes the number of values that
211 // are AffineMap "dimensional" arguments with the remaining values being
212 // AffineMap "symbolic" arguments.
213 DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap,
214 // Denotes the size of each variadic group in '$mapOperands'.
215 DenseI32ArrayAttr:$mapOpGroupSizes);
216 let results = (outs LLZK_ArrayType:$result);
218 // Define builders manually so inference of operand layout attributes is not
220 let skipDefaultBuilders = 1;
221 let builders = [OpBuilder<(ins "::llzk::array::ArrayType":$result,
222 CArg<"::mlir::ValueRange", "{}">:$elements)>,
223 OpBuilder<(ins "::llzk::array::ArrayType":$result,
224 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
225 "::mlir::DenseI32ArrayAttr":$numDimsPerMap)>,
227 (ins "::llzk::array::ArrayType":$result,
228 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
229 "::llvm::ArrayRef<int32_t>":$numDimsPerMap),
231 build($_builder, $_state, result, mapOperands, odsBuilder.getDenseI32ArrayAttr(numDimsPerMap));
234 // This uses the custom parseInferredArrayType function to compute the type
235 // of '$elements' to match the type of '$result', except when '$elements'
236 // is empty, then the type of '$elements' must also be empty (size == 0).
237 // The if-then-else has '$elements' second so that an empty '$elements' list
238 // can be parsed when neither of these is specified.
239 let assemblyFormat = [{
240 ( `{` custom<MultiDimAndSymbolList>($mapOperands, $numDimsPerMap)^ `}` ) : ( $elements )?
242 `` custom<InferredArrayType>(type($elements), ref($elements), ref(type($result)))
243 custom<AttrDictWithWarnings>(attr-dict, prop-dict)
246 let extraClassDeclaration = [{
248 static ::mlir::ParseResult parseInferredArrayType(::mlir::OpAsmParser &parser,
249 ::llvm::SmallVector<::mlir::Type,1> &elementsTypes,
250 ::mlir::ArrayRef<::mlir::OpAsmParser::UnresolvedOperand> elements,
251 ::mlir::Type resultType
254 static void printInferredArrayType(::mlir::OpAsmPrinter &printer, CreateArrayOp,
255 ::mlir::TypeRange, ::mlir::OperandRange, ::mlir::Type
258 static ::llvm::SmallVector<::mlir::Type> resultTypeToElementsTypes(::mlir::Type resultType);
265 : ScalarArrayAccessOp<
266 "read", true, [ArrayTypeElemsUnifyWithResultCustomInfer<"arr_ref">]> {
267 let summary = "read scalar from an array";
269 This operation reads the value from an array at the specified position.
271 Example of 1-dimensional array:
273 %i = arith.constant 0 : index
274 %0 = array.new %a, %b, %c : !array.type<3 x !felt.type>
275 // %1 is now equal to %a
276 %1 = array.read %0[%i] : !array.type<3 x !felt.type>, !felt.type
279 Example of 3-dimensional array:
281 %i = arith.constant 0 : index
282 %j = arith.constant 1 : index
283 %k = arith.constant 4 : index
284 %0 = array.new ... : !array.type<3,10,15 x !felt.type>
285 // %1 is now equal to %a
286 %1 = array.read %0[%i, %j, %k] : !array.type<3,10,15 x !felt.type>, !felt.type
290 let arguments = (ins LLZK_ArrayType:$arr_ref, Variadic<Index>:$indices);
291 let results = (outs ArrayElemType:$result);
293 let assemblyFormat = [{
294 $arr_ref `[` $indices `]` `:` type($arr_ref) `,` type($result) attr-dict
301 : ScalarArrayAccessOp<
302 "write", false, [ArrayElemTypeUnifyWith<"arr_ref", "rvalue">]> {
303 let summary = "write scalar to an array";
305 This operation writes a value into an array at the specified position.
307 Example of 1-dimensional array:
309 %i = arith.constant 0 : index
311 %1 = array.new %a, %b, %c : !array.type<3 x !felt.type>
312 // The array now is [%0, %b, %c]
313 array.write %1[%i] = %0 : !array.type<3 x !felt.type>, !felt.type
316 Example of 2-dimensional array:
318 %i = arith.constant 0 : index
319 %j = arith.constant 0 : index
321 %1 = array.new %a, %b, %c, %d : !array.type<2,2 x !felt.type>
322 // The array now is [[%0, %b], [%c, %d]]
323 array.write %1[%i, %j] = %0 : !array.type<2,2 x !felt.type>, !felt.type
327 let arguments = (ins LLZK_ArrayType:$arr_ref, Variadic<Index>:$indices,
328 ArrayElemType:$rvalue);
330 let assemblyFormat = [{
331 $arr_ref `[` $indices `]` `=` $rvalue `:` type($arr_ref) `,` type($rvalue) attr-dict
337def LLZK_ExtractArrayOp
338 : RangedArrayAccessOp<"extract",
339 true, [InferTypeOpAdaptorWithIsCompatible]> {
340 let summary = "read subarray from a multi-dimensional array";
342 This operation takes an N-dimensional array and K indices and extracts the
343 (N-K)-dimensional array by applying the given indices to the first `K`
344 dimensions of the array. Error if `K >= N`. Use `array.read` instead if `K == N`.
346 Extracting a 1-D array from 3-D array by selecting the index of 2 dimensions:
348 %i = arith.constant 1 : index
349 %0 = array.new ... : !array.type<3,10,15 x !felt.type>
350 %1 = array.extract %0[%i,%i] : !array.type<3,10,15 x !felt.type>, !array.type<15 x !felt.type>
353 Extracting 1-D arrays for subcomponents:
355 scf.for %iv = %lb to %up step %step {
356 %p = array.extract %in[%iv] : !array.type<@N,2 x !felt.type>
357 %c = array.read %a[%iv] : !array.type<@N x !struct.type<@SubC>>, !struct.type<@SubC>
358 function.call @SubC::@constrain(%c, %p) : (!struct.type<@SubC>, !array.type<2 x !felt.type>) -> ()
363 let arguments = (ins LLZK_ArrayType:$arr_ref, Variadic<Index>:$indices);
364 let results = (outs LLZK_ArrayType:$result);
366 let assemblyFormat = [{
367 $arr_ref `[` $indices `]` `:` type($arr_ref) attr-dict
371def LLZK_InsertArrayOp : RangedArrayAccessOp<"insert", false> {
372 let summary = "write subarray into a multi-dimensional array";
374 This operation takes an N-dimensional array, K indices, and an (N+K)-dimensional
375 array and inserts the N-dimensional array into the (N+K)-dimensional array at the
376 position specified by applying the given indices to the first `K` dimensions of
377 the (N+K)-dimensional array. Use `array.write` instead if `N == 0` (LLZK array type
378 must have at least 1 dimension so a 0-dimensional array cannot exist anyway).
380 Inserting 1-D arrays into a 2-D array:
382 %c = array.new : !array.type<2,3 x index>
383 // Array %c is uninitialized [[?, ?, ?], [?, ?, ?]]
384 %0 = arith.constant 0 : index
385 %a = array.new %r, %s, %t : !array.type<3 x index>
386 array.insert %c[%0] = %a : !array.type<2,3 x index>, !array.type<3 x index>
387 // Array %c is now [[%r, %s, %t], [?, ?, ?]]
388 %1 = arith.constant 1 : index
389 %b = array.new %x, %y, %z : !array.type<3 x index>
390 array.insert %c[%1] = %b : !array.type<2,3 x index>, !array.type<3 x index>
391 // Array %c is now [[%r, %s, %t], [%x, %y, %z]]
395 let arguments = (ins LLZK_ArrayType:$arr_ref, Variadic<Index>:$indices,
396 LLZK_ArrayType:$rvalue);
398 let assemblyFormat = [{
399 $arr_ref `[` $indices `]` `=` $rvalue `:` type($arr_ref) `,` type($rvalue) attr-dict
405def LLZK_ArrayLengthOp
406 : ArrayDialectOp<"len", [Pure,
407 DeclareOpInterfaceMethods<SymbolUserOpInterface>,
408 DeclareOpInterfaceMethods<ArrayRefOpInterface>]> {
409 let summary = "return the length of an array";
411 This operation returns the size of the specified dimension of an array.
415 %a = array.new : !array.type<2,3 x !felt.type>
416 %0 = arith.constant 0 : index
417 %x = array.len %a, %0 : !array.type<2,3 x !felt.type> // result is 2
418 %1 = arith.constant 1 : index
419 %y = array.len %a, %1 : !array.type<2,3 x !felt.type> // result is 3
423 let arguments = (ins LLZK_ArrayType:$arr_ref, Index:$dim);
424 let results = (outs Index:$length);
426 let assemblyFormat = [{ $arr_ref `,` $dim `:` type($arr_ref) attr-dict }];
428 let extraClassDeclaration = [{
429 /// Gets the type of the referenced base array.
430 inline ::llzk::array::ArrayType getArrRefType() {
431 return ::llvm::cast<ArrayRefOpInterface>(getOperation()).getArrRefType();
436#endif // LLZK_ARRAY_OPS