1//===-- OpTraits.td - Custom Trait classes for ops ---------*- 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//===----------------------------------------------------------------------===//
10#ifndef LLZK_SHARED_OP_HELPER
11#define LLZK_SHARED_OP_HELPER
13include "mlir/Interfaces/InferTypeOpInterface.td"
14include "mlir/Interfaces/SideEffectInterfaces.td"
15include "mlir/IR/SymbolInterfaces.td"
17// Do not use this directly. Use LLZKSymbolTable.
18def LLZKSymbolTableImplTrait : NativeOpTrait<"LLZKSymbolTableImplTrait"> {
19 string cppNamespace = "::llzk";
22// Always use this trait instead of builtin `SymbolTable` trait.
24// This trait avoids an assertion failure in the `SymbolTable` class constructor
25// when the symbol table is malformed (e.g. duplicate symbols), instead allowing
26// it to produce an error diagnostic. This can happen due to the implementations
27// of `verifySymbolUses()` that do symbol lookups from the root module. These
28// are called before ancestor module symbol tables are verified, thus leading to
29// an assertion failure before the verifier would produce a friendly diagnostic.
30// This trait handles that by injecting the usual symbol table verification on
31// ancestor symbol tables before performing verification of the current symbol
33def LLZKSymbolTable : TraitList<[LLZKSymbolTableImplTrait, SymbolTable]>;
35/// Verify that the operation has a parent of type `op` somewhere in its
37class HasAncestor<string op> : ParamNativeOpTrait<"HasAncestor", op>,
39 string cppNamespace = "::llzk";
42/// Verify that the operation has a parent of one of the `ops` types somewhere
44class HasAncestorOf<list<string> ops>
45 : ParamNativeOpTrait<"HasAncestor", !interleave(ops, ", ")>,
47 string cppNamespace = "::llzk";
50// Implements verification for ops with an affine_map instantiation list. These
51// ops are expected to contain the following in their `arguments` list:
52// - VariadicOfVariadic<Index, "mapOpGroupSizes">:$mapOperands
53// - DefaultValuedAttr<DenseI32ArrayAttr, "{}">:$numDimsPerMap
54// - DenseI32ArrayAttr:$mapOpGroupSizes
55// Additionally, if the op also has the `AttrSizedOperandSegments` trait, the
56// parameter of this trait specifies the index within the `operandSegmentSizes`
57// attribute associated with the `$mapOperands` argument, otherwise the
58// parameter is ignored. All of these attributes are necessary because MLIR
59// stores all operands for an Op in a single list. These attributes specify how
60// the list of operands is split into logical pieces for the operand components.
62// For example, suppose the `CreateArrayOp` is used to create an array with type
63// `!array.type<affine_map<(d0)->(d0)>,affine_map<(d0)[s0]->(d0+s0)> x i1>`
65// 1) `CreateArrayOp` requires the `AttrSizedOperandSegments` trait because it
66// defines two variadic arguments: `$elements` and `$mapOperands` (in that
67// order). Thus, the `operandSegmentSizes` attribute is automatically defined
68// to specify the number of operands that belong to each variadic argument:
69// `operandSegmentSizes = array<i32: COUNT($elements), COUNT($mapOperands)>`
70// In the case of `CreateArrayOp`, one of those sizes will always be 0 because
71// its assembly format has `$elements` and `$mapOperands` as alternatives. In
72// this example, `COUNT($elements) = 0` and `COUNT($mapOperands) = 3` (this is
73// the sum of operand count for all affine_map that are used as array dimensions
74// in the result array type).
76// 2) The `$mapOpGroupSizes` attribute groups the `$mapOperands` per affine_map.
77// This implies that their sum equals `COUNT($mapOperands)`. In the example, the
78// first affine_map has 1 parameter and the second has 2 so:
79// `mapOpGroupSizes = array<i32: 1, 2>`
81// 3) Finally, the `$numDimsPerMap` attribute splits the `$mapOperands` in each
82// group into the dimensional and symbolic inputs for each affine_map.
83// Dimensional inputs appear between the () and symbolic inputs appear between
84// the []. LLZK mainly uses dimensional inputs and not symbolic inputs but both
85// are fully supported. The length of `$numDimsPerMap` must equal the length of
86// `$mapOpGroupSizes` and each element of `$numDimsPerMap` must be less than the
87// corresponding element of `$mapOpGroupSizes`. In the example, the both
88// affine_map instantiations in the array type have 1 dimensional input so:
89// `numDimsPerMap = array<i32: 1, 1>`
91// It is also recomended to use `custom<AttrDictWithWarnings>(attr-dict)` in the
92// assembly format (or the associated parse/print functions directly) to parse
93// the attribute dictionary in these ops and present warnings if the
94// aforementioned attributes are manually specified.
95class VerifySizesForMultiAffineOps<int operandSegmentIndex>
96 : ParamNativeOpTrait<"VerifySizesForMultiAffineOps",
97 ""#operandSegmentIndex>,
99 string cppNamespace = "::llzk";
102// Identical to `TypesMatchWith` with `rhsArg = result`. This should be used
103// instead of `TypesMatchWith` when custom return type inference is necessary
104// (via `InferTypeOpAdaptor*`) because MLIR has special handing for
105// `TypesMatchWith` that results in "error: redefinition of 'inferReturnTypes'".
106class TypeMatchResultWith<string lhsArg, string lhsSummary = lhsArg,
108 string comparator = "std::equal_to<>()">
110 "result type matches with "#lhsSummary#" type",
111 CPred<comparator#"("#!subst("$_self", "$"#lhsArg#".getType()",
112 transform)#", $result.getType())">> {
114 string rhs = "result";
115 string transformer = transform;
118// Like TypesUnify with `rhsArg = "result"`
119class TypeUnifyWithResult<string lhsArg, string lhsSummary = lhsArg,
120 string transform = "$_self">
121 : TypeMatchResultWith<lhsArg, lhsSummary, transform, "::llzk::typesUnify">;
123// Implementation of TypesMatchWith for Variadic `rhsArg` that returns success
124// if `rhsArg` is empty.
125class VariadicTypesMatchWith<string summary, string lhsArg, string rhsArg,
127 string comparator = "std::equal_to<>()">
129 summary, lhsArg, rhsArg, transform,
130 "get"#snakeCaseToCamelCase<rhsArg>.ret#"().empty() || "#comparator>;
132// Type constraint `llzk::typesUnify(transform(lhs.getType()), rhs.getType())`.
133// If either parameter is `$result` it is recommended to use TypeUnifyWithResult
134// instead as this is likely too restrictive when type variables are involved.
135class TypesUnify<string lhsArg, string rhsArg, string lhsSummary = lhsArg,
136 string rhsSummary = rhsArg, string transform = "$_self">
137 : TypesMatchWith<rhsSummary#" type matches with "#lhsSummary#" type",
138 lhsArg, rhsArg, transform, "::llzk::typesUnify">;
140// Returns success if `elementArg` unifies with the `arrayArg` element type.
141class ArrayElemTypeUnifyWith<string arrayArg, string elementArg>
143 arrayArg, elementArg, arrayArg#" element", elementArg,
144 "::llvm::cast<::llzk::array::ArrayType>($_self).getElementType()">;
146// Returns success if `$result` unifies with the `arrayArg` element type.
147class ArrayElemTypeUnifyWithResult<string arrayArg>
148 : TypeMatchResultWith<
149 arrayArg, arrayArg#" element",
150 "::llvm::cast<::llzk::array::ArrayType>($_self).getElementType()",
151 "::llzk::typesUnify">;
153// ArrayElemTypeUnifyWithResult + InferTypeOpAdaptorWithIsCompatible (i.e.
154// generate inferReturnTypes() and isCompatibleReturnTypes() functions)
155class ArrayTypeElemsUnifyWithResultCustomInfer<string arrayArg>
156 : TraitList<[ArrayElemTypeUnifyWithResult<arrayArg>,
157 InferTypeOpAdaptorWithIsCompatible]>;
159#endif // LLZK_SHARED_OP_HELPER