1 //===- ReshapeOpsUtils.cpp - Utilities used by structured ops -------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "mlir/Dialect/Utils/ReshapeOpsUtils.h"
10 
11 #include "mlir/IR/AffineMap.h"
12 #include "mlir/IR/Builders.h"
13 
14 #include <numeric>
15 
16 using namespace mlir;
17 
18 constexpr StringRef mlir::getReassociationAttrName() { return "reassociation"; }
19 
20 Optional<SmallVector<ReassociationIndices>>
21 mlir::getReassociationIndicesForReshape(ShapedType sourceType,
22                                         ShapedType targetType) {
23   // Make the sourceType greater rank than the targetType. If they are same
24   // rank, then its an unsupported reshape op.
25   if (sourceType.getRank() == targetType.getRank())
26     return llvm::None;
27   if (sourceType.getRank() < targetType.getRank())
28     std::swap(sourceType, targetType);
29 
30   ArrayRef<int64_t> sourceShape = sourceType.getShape();
31   ArrayRef<int64_t> targetShape = targetType.getShape();
32   unsigned sourceDim = 0;
33   SmallVector<ReassociationIndices> reassociationMap;
34   reassociationMap.reserve(targetType.getRank());
35 
36   ReassociationIndices currIndices;
37   int64_t prodOfCollapsedDims = 1;
38   while (sourceDim < sourceShape.size()) {
39     unsigned targetDim = reassociationMap.size();
40 
41     // If all the dimensions of the targetShape are exhausted, then the
42     // remaining dims in the source shape must be all 1s. So for such cases, set
43     // 1 as the target shape. The actual reassociation indices will be handled
44     // later.
45     int64_t currTargetShape =
46         (targetDim < targetType.getRank() ? targetShape[targetDim] : 1);
47     while (sourceShape[sourceDim] != ShapedType::kDynamicSize &&
48            prodOfCollapsedDims * sourceShape[sourceDim] < currTargetShape &&
49            sourceDim < sourceShape.size()) {
50       prodOfCollapsedDims *= sourceShape[sourceDim];
51       currIndices.push_back(sourceDim++);
52     }
53 
54     // If the current expanded dimension is dynamic, then the collapsed
55     // dimensions should also be dynamic and product of all previous unprocessed
56     // dimensions of the expanded shape should be 1.
57     if (sourceShape[sourceDim] == ShapedType::kDynamicSize &&
58         (currTargetShape != ShapedType::kDynamicSize ||
59          prodOfCollapsedDims != 1))
60       return llvm::None;
61 
62     // If the collapsed dim is dynamic, the current expanded dim should also
63     // be dynamic.
64     if (currTargetShape == ShapedType::kDynamicSize &&
65         sourceShape[sourceDim] != ShapedType::kDynamicSize)
66       return llvm::None;
67 
68     // For static shapes, if the product of dimensions of the expanded shape
69     // should match the collapsed dimension shape.
70     if (prodOfCollapsedDims * sourceShape[sourceDim] != currTargetShape)
71       return llvm::None;
72 
73     currIndices.push_back(sourceDim++);
74     // If the reassociation is empty but the currIndices is not, this by
75     // definition is folding unit-dimensions with the result being scalar type.
76     // So only append the `currIndices` if reassociation map is not empty.
77     if (targetDim == targetShape.size()) {
78       if (!reassociationMap.empty() && !currIndices.empty())
79         reassociationMap.back().append(currIndices.begin(), currIndices.end());
80       // Break out of the loops. We should be done here.
81       break;
82     }
83     reassociationMap.emplace_back(ReassociationIndices{});
84     std::swap(reassociationMap.back(), currIndices);
85     prodOfCollapsedDims = 1;
86   }
87   // All the dimensions in the two shapes must have been processed.
88   if (reassociationMap.size() != targetShape.size() ||
89       sourceDim != sourceShape.size())
90     return llvm::None;
91   return reassociationMap;
92 }
93 
94 ParseResult mlir::parseReshapeLikeOp(OpAsmParser &parser,
95                                      OperationState &result) {
96   // Parse the operand.
97   OpAsmParser::OperandType src;
98   if (parser.parseOperand(src))
99     return failure();
100 
101   // Parse reassociation indices.
102   Builder &b = parser.getBuilder();
103   SmallVector<Attribute, 4> reassociation;
104   if (parser.parseLSquare())
105     return failure();
106 
107   while (true) {
108     if (succeeded(parser.parseOptionalRSquare()))
109       break;
110     if (parser.parseLSquare())
111       return failure();
112     SmallVector<int64_t> indices;
113     while (true) {
114       int64_t index;
115       if (parser.parseInteger(index))
116         return failure();
117       indices.push_back(index);
118 
119       if (succeeded(parser.parseOptionalComma()))
120         continue;
121       if (failed(parser.parseRSquare()))
122         return failure();
123       break;
124     }
125     reassociation.push_back(b.getI64ArrayAttr(indices));
126     if (succeeded(parser.parseOptionalComma()))
127       continue;
128     if (failed(parser.parseRSquare()))
129       return failure();
130     break;
131   }
132 
133   result.addAttribute(getReassociationAttrName(),
134                       b.getArrayAttr(reassociation));
135 
136   // Parse optional attributes.
137   parser.parseOptionalAttrDict(result.attributes);
138 
139   // Parse types.
140   Type srcType;
141   Type resultType;
142   if (parser.parseColon() || parser.parseType(srcType) ||
143       parser.resolveOperand(src, srcType, result.operands) ||
144       parser.parseKeyword("into") || parser.parseType(resultType))
145     return failure();
146   result.addTypes(resultType);
147   return success();
148 }
149 
150 Optional<SmallVector<ReassociationIndices>> mlir::composeReassociationIndices(
151     ArrayRef<ReassociationIndices> producerReassociations,
152     ArrayRef<ReassociationIndices> consumerReassociations,
153     MLIRContext *context) {
154   SmallVector<ReassociationIndices> composedIndices;
155   // Make the producer the larger sized vector. If they are of same size, the
156   // resulting reshape is not a supported reshape op.
157   if (producerReassociations.size() == consumerReassociations.size())
158     return llvm::None;
159   if (producerReassociations.size() < consumerReassociations.size())
160     std::swap(producerReassociations, consumerReassociations);
161 
162   // Handle the corner case of the result being a rank 0 shaped type. Return an
163   // empty reassociation.
164   if (consumerReassociations.empty())
165     return composedIndices;
166 
167   size_t consumerDims = std::accumulate(
168       consumerReassociations.begin(), consumerReassociations.end(), 0,
169       [](size_t all, ReassociationIndicesRef indices) {
170         return all + indices.size();
171       });
172   if (producerReassociations.size() != consumerDims)
173     return llvm::None;
174 
175   for (ReassociationIndicesRef consumerIndices : consumerReassociations) {
176     ReassociationIndices reassociations;
177     for (int64_t consumerIndex : consumerIndices) {
178       for (int64_t producerIndex : producerReassociations[consumerIndex])
179         reassociations.push_back(producerIndex);
180     }
181     composedIndices.push_back(std::move(reassociations));
182   }
183   return composedIndices;
184 }
185 
186 bool mlir::isReassociationValid(ArrayRef<AffineMap> reassociation,
187                                 int *invalidIndex) {
188   if (reassociation.empty())
189     return true;
190   unsigned nDims = reassociation[0].getNumDims();
191   unsigned nextExpectedDim = 0;
192   for (auto it : llvm::enumerate(reassociation)) {
193     auto m = it.value();
194     if (m.getNumDims() != nDims || m.getNumSymbols() != 0) {
195       if (invalidIndex)
196         *invalidIndex = it.index();
197       return false;
198     }
199     for (auto e : m.getResults()) {
200       auto d = e.dyn_cast<AffineDimExpr>();
201       if (!d || d.getPosition() != nextExpectedDim++) {
202         if (invalidIndex)
203           *invalidIndex = it.index();
204         return false;
205       }
206     }
207   }
208   if (nextExpectedDim != nDims) {
209     if (invalidIndex)
210       *invalidIndex = reassociation.size() - 1;
211     return false;
212   }
213   return true;
214 }
215