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 2026 Project LLZK
6// SPDX-License-Identifier: Apache-2.0
8//===----------------------------------------------------------------------===//
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"
19include "mlir/IR/OpBase.td"
20include "mlir/IR/OpAsmInterface.td"
21include "mlir/IR/SymbolInterfaces.td"
22include "mlir/Interfaces/SideEffectInterfaces.td"
24class PODDialectOp<string mnemonic, list<Trait> traits = []>
25 : Op<PODDialect, mnemonic, traits>;
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();
38// isRead: read(1) vs write(0) ops
39class ScalarPODAccessOp<string mnemonic, bit isRead, list<Trait> traits = []>
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);
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);
64 /// Required by PromotableMemOpInterface / mem2reg pass
65 bool $cppClass::loadsFrom(const ::mlir::MemorySlot &slot) {
66 return }]#!if(isRead, "getPodRef() == slot.ptr", "false")#[{;
69 /// Required by PromotableMemOpInterface / mem2reg pass
70 bool $cppClass::storesTo(const ::mlir::MemorySlot &slot) {
71 return }]#!if(isRead, "false", "getPodRef() == slot.ptr")#[{;
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")#[{;
79 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
80 bool $cppClass::loadsFromDiscardableAllocation(::mlir::Value ptr) {
81 return }]#!if(isRead, "getPodRef() == ptr", "false")#[{;
84 /// Required by DiscardableAllocationAccessorOpInterface / unused allocation cleanup.
85 bool $cppClass::storesToDiscardableAllocation(::mlir::Value ptr) {
86 return }]#!if(isRead, "false",
87 "getPodRef() == ptr && getValue() != ptr")#[{;
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")#[{;
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";
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.
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.
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.
121 // Empty pod instance
122 %0 = pod.new : !pod.type<[]>
124 // Uninitialized/nondeterministic pod instance
125 %0 = pod.new : !pod.type<[@n: !felt.type]>
127 // Initialized pod instance
129 %1 = pod.new { @n = %0 } : !pod.type<[@n: !felt.type]>
131 // Another one, but with 2 fields
134 %2 = pod.new { @n = %0, @inv = %1 } : !pod.type<[@n: !felt.type, @inv: !felt.type]>
138 %1 = pod.new { @n = %0 } : !pod.type<[@n: !felt.type, @inv: !felt.type]>
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>]>
145 // Affine map with initialized records
146 %0 = arith.constant 1 : index
147 %c = arith.constant 2 : index
149 %2 = pod.new { @f = %1 }(%0)[%c] : !pod.type<[@f: !felt.type, @a: !array.type<#map, !felt.type>]>
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;
165 (ins CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues),
167 auto resultType = ::llzk::pod::PodType::fromInitialValues($_builder.getContext(), initialValues);
168 build($_builder, $_state, resultType, initialValues);
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)>,
177 (ins "::llzk::pod::PodType":$resultType,
178 "::llvm::ArrayRef<::mlir::ValueRange>":$mapOperands,
179 "::llvm::ArrayRef<int32_t>":$numDimsPerMap,
180 CArg<"::llzk::pod::InitializedRecords", "{}">:$initialValues),
182 build($_builder, $_state, resultType, mapOperands,
183 $_builder.getDenseI32ArrayAttr(numDimsPerMap), initialValues);
185 let hasCustomAssemblyFormat = 1;
188 let extraClassDeclaration = [{
189 ::mlir::SmallVector<::llzk::pod::RecordValue> getInitializedRecordValues();
193def LLZK_ReadPodOp : ScalarPODAccessOp<"read", 1> {
194 let summary = "reads the contents of a plain-old-data struct record";
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.
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.
210 %1 = pod.read %0[@sym] : !pod.type<[@sym: !type, ...]>, !type
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
220 let useCustomPropertiesEncoding = 1;
224def LLZK_WritePodOp : ScalarPODAccessOp<"write", 0> {
225 let summary = "writes content into a plain-old-data struct record";
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
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.
239 Writing a record that was written or initialized earlier overwrites the previous value.
243 pod.write %0[@sym] = %1 : !pod.type<[@sym: !type, ...]>, !type
247 let arguments = (ins Arg<LLZK_PODType, "the pod to write into">:$pod_ref,
248 StrAttr:$record_name, AnyLLZKType:$value);
250 let assemblyFormat = [{
251 $pod_ref `[` custom<RecordName>($record_name) `]` `=` $value `:` type($pod_ref) `,` type($value) attr-dict
253 let useCustomPropertiesEncoding = 1;
257#endif // LLZK_POD_OPS