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