1 //===- LinalgInterfaces.cpp - Linalg interfaces implementation ------------===// 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/Linalg/IR/LinalgInterfaces.h" 10 11 #include "mlir/Dialect/Affine/IR/AffineOps.h" 12 #include "mlir/Dialect/MemRef/IR/MemRef.h" 13 #include "mlir/IR/AffineExprVisitor.h" 14 #include "mlir/IR/AffineMap.h" 15 #include "mlir/IR/TypeUtilities.h" 16 #include "llvm/ADT/SmallSet.h" 17 18 using namespace mlir; 19 using namespace mlir::linalg; 20 21 /// Include the definitions of the copy operation interface. 22 #include "mlir/Dialect/Linalg/IR/LinalgInterfaces.cpp.inc" 23 24 //===----------------------------------------------------------------------===// 25 // ContractionOpInterface implementation 26 //===----------------------------------------------------------------------===// 27 28 /// Return true if the use-def chain from `v` to `from` consists of 0 or more 29 /// unary single-operand operations. 30 // TODO: relax to multi-operands with constants, which are technically unary ops 31 // as needed (e.g. add5). 32 static bool isChainOfUnaryOpsFrom(Value v, Value from) { 33 while (true) { 34 if (v == from) 35 return true; 36 Operation *op = v.getDefiningOp(); 37 if (!op || op->getNumOperands() != 1) 38 return false; 39 v = op->getOperand(0); 40 }; 41 } 42 43 /// Return the unique instance of OpType in `block` if it is indeed unique. 44 /// Return null if none or more than 1 instances exist. 45 template <typename OpType> 46 static OpType getSingleOpOfType(Block &block) { 47 OpType res = nullptr; 48 block.walk([&](OpType op) { 49 if (res) { 50 res = nullptr; 51 return WalkResult::interrupt(); 52 } 53 res = op; 54 return WalkResult::advance(); 55 }); 56 return res; 57 } 58 59 /// Detect whether res is any permutation of `u5(u1(c) + u2(u3(a) * u4(b)))` 60 /// on the field (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent 61 /// unary operations that may change the type. 62 template <typename AddOpType, typename MulOpType> 63 static bool isAddMul(Block &block) { 64 if (block.getNumArguments() != 3) 65 return false; 66 Operation *yieldOp = block.getTerminator(); 67 if (yieldOp->getNumOperands() != 1) 68 return false; 69 70 AddOpType addOp = getSingleOpOfType<AddOpType>(block); 71 MulOpType mulOp = getSingleOpOfType<MulOpType>(block); 72 if (!addOp || !mulOp) 73 return false; 74 75 Value argA = block.getArgument(0), argB = block.getArgument(1); 76 Value a = mulOp->getOperand(0), b = mulOp->getOperand(1); 77 Value mul = mulOp->getResult(0); 78 Value argC = block.getArgument(2); 79 Value c1 = addOp->getOperand(0), c2 = addOp->getOperand(1); 80 Value add = addOp->getResult(0); 81 Value res = yieldOp->getOperand(0); 82 // Result traces back to add. 83 auto un = isChainOfUnaryOpsFrom; 84 bool success = un(res, add); 85 // One of the operands of add traces back to argC, the other to the mul. 86 success |= (un(c1, argC) && un(c2, mul)) || ((un(c1, mul)) && un(c2, argC)); 87 // One of the operands of mul traces back to argA, the other to argB. 88 success |= (un(a, argA) && un(b, argB)) || ((un(a, argB)) && un(b, argA)); 89 return success; 90 } 91 92 enum MatchContractionResult { 93 Success = 0, 94 NotLinalgOp, 95 WrongNumOperands, 96 NoReduction, 97 NotProjectedPermutations, 98 NotAddMul 99 }; 100 static MatchContractionResult isContractionInterfaceImpl(Operation *op) { 101 auto linalgOp = dyn_cast<linalg::LinalgOp>(op); 102 if (!linalgOp) 103 return MatchContractionResult::NotLinalgOp; 104 if (linalgOp.getNumInputs() != 2 || linalgOp.getNumOutputs() != 1) 105 return MatchContractionResult::WrongNumOperands; 106 auto mapRange = linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>(); 107 if (linalgOp.getNumReductionLoops() == 0) 108 return MatchContractionResult::NoReduction; 109 if (llvm::any_of(mapRange, 110 [](AffineMap m) { return !m.isProjectedPermutation(); })) 111 return MatchContractionResult::NotProjectedPermutations; 112 // TODO: more fields than add/mul. 113 if (!isAddMul<AddFOp, MulFOp>(linalgOp->getRegion(0).front()) && 114 !isAddMul<AddIOp, MulIOp>(linalgOp->getRegion(0).front())) 115 return MatchContractionResult::NotAddMul; 116 return MatchContractionResult::Success; 117 } 118 119 bool mlir::linalg::isaContractionOpInterface(LinalgOp linalgOp) { 120 if (!linalgOp) 121 return false; 122 Operation *op = linalgOp.getOperation(); 123 return isa<ContractionOpInterface>(op) || 124 (isContractionInterfaceImpl(op) == MatchContractionResult::Success); 125 } 126 127 /// Verify that a LinalgOp `op` is a contraction. 128 /// A Linalg contraction is defined in general terms: 129 /// 1. Has 2 input and 1 output shapes. 130 /// 2. Has at least one reduction dimension. 131 /// 3. Has only projected permutation indexing maps. 132 /// 4. its body computes `u5(u1(c) + u2(u3(a) * u4(b)))` on some field 133 /// (AddOpType, MulOpType), where u1, u2, u3, u4 and u5 represent scalar unary 134 /// operations that may change the type (e.g. for mixed-precision). 135 /// As a consequence, when vectorization of such an op occurs, the only special 136 /// behavior is that the (unique) MulOpType is vectorized into a 137 /// `vector.contract`. All other ops are handled in a generic fashion. 138 /// In the future, we may wish to allow more input arguments and elementwise and 139 /// constant operations that do not involve the reduction dimension(s). 140 LogicalResult mlir::linalg::detail::verifyContractionInterface(Operation *op) { 141 auto res = isContractionInterfaceImpl(op); 142 if (res == MatchContractionResult::NotLinalgOp) 143 return op->emitError("expected a LinalgOp"); 144 if (res == MatchContractionResult::WrongNumOperands) 145 return op->emitError("expected op with 2 inputs and 1 outputs"); 146 if (res == MatchContractionResult::NoReduction) 147 return op->emitError("expected at least a reduction loop"); 148 if (res == MatchContractionResult::NotProjectedPermutations) 149 return op->emitError("expected all indexings to be projected permutations"); 150 if (res == MatchContractionResult::NotAddMul) 151 return op->emitError("(add, mul) operations not found"); 152 return success(); 153 } 154 155 //===----------------------------------------------------------------------===// 156 // StructuredOpInterface implementation 157 //===----------------------------------------------------------------------===// 158 159 OpOperandVector::operator SmallVector<Value>() { 160 SmallVector<Value> result; 161 result.reserve(this->size()); 162 llvm::transform(*this, std::back_inserter(result), 163 [](OpOperand *opOperand) { return opOperand->get(); }); 164 return result; 165 } 166 167 /// Fully compose map with operands and canonicalize the result. 168 /// Return the `createOrFold`'ed AffineApply op. 169 static Value createFoldedComposedAffineApply(OpBuilder &b, Location loc, 170 AffineMap map, 171 ValueRange operandsRef) { 172 SmallVector<Value, 4> operands(operandsRef.begin(), operandsRef.end()); 173 fullyComposeAffineMapAndOperands(&map, &operands); 174 canonicalizeMapAndOperands(&map, &operands); 175 return b.createOrFold<AffineApplyOp>(loc, map, operands); 176 } 177 178 SmallVector<Value, 4> mlir::linalg::applyMapToValues(OpBuilder &b, Location loc, 179 AffineMap map, 180 ValueRange values) { 181 SmallVector<Value, 4> res; 182 res.reserve(map.getNumResults()); 183 unsigned numDims = map.getNumDims(), numSym = map.getNumSymbols(); 184 // For each `expr` in `map`, applies the `expr` to the values extracted from 185 // ranges. If the resulting application can be folded into a Value, the 186 // folding occurs eagerly. 187 for (auto expr : map.getResults()) { 188 AffineMap map = AffineMap::get(numDims, numSym, expr); 189 res.push_back(createFoldedComposedAffineApply(b, loc, map, values)); 190 } 191 return res; 192 } 193 194 SmallVector<Value, 4> LinalgOp::createFlatListOfOperandDims(OpBuilder &b, 195 Location loc) { 196 SmallVector<Value, 4> res; 197 for (Value v : getShapedOperands()) { 198 ShapedType t = v.getType().template cast<ShapedType>(); 199 for (unsigned i = 0, e = t.getRank(); i < e; ++i) 200 res.push_back(b.createOrFold<memref::DimOp>(loc, v, i)); 201 } 202 return res; 203 } 204 205 SmallVector<int64_t, 4> LinalgOp::createFlatListOfOperandStaticDims() { 206 SmallVector<int64_t, 4> res; 207 for (Value v : getShapedOperands()) { 208 ShapedType t = v.getType().template cast<ShapedType>(); 209 assert(t.hasStaticShape() && "expected operands to have static shapes"); 210 llvm::append_range(res, t.getShape()); 211 } 212 return res; 213 } 214 215 SmallVector<Range, 4> LinalgOp::createLoopRanges(OpBuilder &b, Location loc) { 216 AffineMap map = getLoopsToShapesMap(); 217 unsigned numDims = map.getNumDims(), numRes = map.getNumResults(); 218 auto viewSizes = createFlatListOfOperandDims(b, loc); 219 SmallVector<Range, 4> res(numDims); 220 Value zeroVal = b.create<ConstantIndexOp>(loc, 0); 221 Value oneVal = b.create<ConstantIndexOp>(loc, 1); 222 for (unsigned idx = 0; idx < numRes; ++idx) { 223 auto result = map.getResult(idx); 224 if (auto d = result.dyn_cast<AffineDimExpr>()) { 225 if (res[d.getPosition()].offset) 226 continue; 227 res[d.getPosition()] = Range{zeroVal, viewSizes[idx], oneVal}; 228 } 229 } 230 return res; 231 } 232 233 SmallVector<int64_t, 4> LinalgOp::computeStaticLoopSizes() { 234 AffineMap map = getLoopsToShapesMap(); 235 unsigned numDims = map.getNumDims(), numRes = map.getNumResults(); 236 SmallVector<int64_t, 4> allShapeSizes = createFlatListOfOperandStaticDims(); 237 SmallVector<int64_t, 4> res(numDims, 0); 238 for (unsigned idx = 0; idx < numRes; ++idx) { 239 auto result = map.getResult(idx); 240 if (auto d = result.dyn_cast<AffineDimExpr>()) 241 res[d.getPosition()] = allShapeSizes[idx]; 242 } 243 return res; 244 } 245 246 /// Visitor to check if any of the given set of positions from AffineDimExprs 247 /// are used within an AffineExpr. 248 struct HasAffineDimExprVisitor 249 : public AffineExprVisitor<HasAffineDimExprVisitor, bool> { 250 HasAffineDimExprVisitor(llvm::SmallSet<unsigned, 4> &positions) 251 : positions(positions) {} 252 253 bool visitAffineBinaryOpExpr(AffineBinaryOpExpr binaryOpExpr) { 254 return visit(binaryOpExpr.getLHS()) || visit(binaryOpExpr.getRHS()); 255 } 256 257 bool visitDimExpr(AffineDimExpr dimExpr) { 258 return positions.count(dimExpr.getPosition()); 259 } 260 261 bool visitConstantExpr(AffineConstantExpr constExpr) { return false; } 262 263 bool visitSymbolExpr(AffineSymbolExpr symbolExpr) { return false; } 264 265 private: 266 llvm::SmallSet<unsigned, 4> positions; 267 }; 268 269 LogicalResult LinalgOp::reifyReturnTypeShapesPerResultDim( 270 OpBuilder &b, SmallVectorImpl<SmallVector<Value>> &reifiedReturnShapes) { 271 // An example that helps understand the logic below. 272 // Consider the following expression O(i+j, j) += A(i,k) * B(k, j) 273 // We want to express the shape of dim 0 of O in terms of shape of the inputs. 274 // This is achieved as follows. 275 // loopsToShapesMap = (d0, d1, d2) -> (d0, d2, d2, d1, d0 + d1, d1) 276 // subMapOfResultShapes = (d0, d1, d2) -> (d0 + d1, d1) 277 // shapesToLoopsMap = (d0, d2, d2, d3, d4, d5) -> (d0, d3, d2) 278 // resultShapesFromInputShapes = subMapOfResultDim.compose(shapesToLoopMap) 279 // = (d0, d1, d2, d3, d4, d5) -> (d0 + d1, d1) 280 AffineMap loopsToShapesMap = getLoopsToShapesMap(); 281 282 // Find the position in the above map that represents the shape of the 283 // result:dim being inferred. 284 auto resultShapesSubMapPos = getResultsPositionInLoopsToShapeMap(); 285 286 /// From loopsToShapesMap extract the submap that represents the shape of the 287 /// (resultIdx, dim) needed. 288 SmallVector<unsigned, 4> resultPosRange = 289 llvm::to_vector<4>(llvm::seq<unsigned>(resultShapesSubMapPos.first, 290 resultShapesSubMapPos.second)); 291 AffineMap loopToResultsShapeMap = loopsToShapesMap.getSubMap(resultPosRange); 292 AffineMap resultShapesFromInputShapesMap = 293 loopToResultsShapeMap.compose(getShapesToLoopsMap()); 294 295 // Check that the result dim map does not contain the positions corresponding 296 // to the outputs. 297 llvm::SmallSet<unsigned, 4> outputDims; 298 llvm::for_each(resultPosRange, 299 [&outputDims](unsigned dim) { outputDims.insert(dim); }); 300 HasAffineDimExprVisitor checkDimExpr(outputDims); 301 Location loc = getOperation()->getLoc(); 302 auto allResultDimValues = 303 applyMapToValues(b, loc, resultShapesFromInputShapesMap, 304 createFlatListOfOperandDims(b, loc)); 305 unsigned pos = 0; 306 ArrayRef<AffineExpr> shapeExprs = resultShapesFromInputShapesMap.getResults(); 307 for (auto resultIdx : llvm::seq<unsigned>(0, getNumOutputs())) { 308 ShapedType resultType = getOutputShapedType(resultIdx); 309 SmallVector<Value> shapes; 310 for (unsigned dim : llvm::seq<unsigned>(0, resultType.getRank())) { 311 if (checkDimExpr.visit(shapeExprs[pos])) 312 shapes.push_back( 313 b.createOrFold<memref::DimOp>(loc, getOutput(resultIdx), dim)); 314 else 315 shapes.push_back(allResultDimValues[pos]); 316 pos++; 317 } 318 reifiedReturnShapes.emplace_back(std::move(shapes)); 319 } 320 return success(); 321 } 322 323 LogicalResult mlir::linalg::detail::verifyStructuredOpInterface(Operation *op) { 324 LinalgOp linalgOp = cast<LinalgOp>(op); 325 // Expect at least one input/output operand. 326 // This means an op that constructs a tensor out of indices cannot be a 327 // LinalgOp at the moment. For now this will have to be a special op until we 328 // have output shape operands that are not tensors. 329 int64_t numInputsAndOutputs = linalgOp.getNumInputsAndOutputs(); 330 if (numInputsAndOutputs == 0) 331 return op->emitOpError("expected at least one input/output operand"); 332 if (failed(OpTrait::impl::verifyAtLeastNOperands(op, numInputsAndOutputs))) 333 return failure(); 334 // Should have at least one output tensor per result tensor. 335 // Can also have outbut buffers that do not correspond to results. 336 if (op->getNumResults() > linalgOp.getOutputTensorOperands().size()) 337 return op->emitOpError("unexpected #results > #outputs"); 338 339 // Before checking indexing maps, we need to make sure the attributes 340 // referenced by it are valid. 341 if (linalgOp.hasDynamicIndexingMaps()) 342 if (failed(linalgOp.verifyIndexingMapRequiredAttributes())) 343 return failure(); 344 345 // All shaped operands must be indexed. 346 if (static_cast<int64_t>(linalgOp.indexing_maps().size()) != 347 linalgOp.getNumInputsAndOutputs()) 348 return op->emitOpError("expected the number of indexing_map (") 349 << linalgOp.indexing_maps().size() 350 << ") to be equal to the number of input/output operands (" 351 << linalgOp.getNumInputsAndOutputs() << ")"; 352 353 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 354 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 355 356 // Symbols disallowed. 357 if (indexingMap.getNumSymbols() != 0) 358 return op->emitOpError("unexpected symbols in indexing_map #") 359 << opOperand->getOperandNumber(); 360 361 // Domain must be consistent. 362 unsigned numLoops = linalgOp.getNumLoops(); 363 if (indexingMap.getNumDims() != numLoops) 364 return op->emitOpError("expected indexing_map #") 365 << opOperand->getOperandNumber() << " to have " << numLoops 366 << " dim(s) to match the number of loops"; 367 368 int64_t rank = linalgOp.getRank(opOperand); 369 if (indexingMap.getNumResults() != rank) 370 return op->emitOpError("expected shaped value rank (") 371 << rank << ") to match the result rank of indexing_map #" 372 << opOperand->getOperandNumber() << " (" 373 << indexingMap.getNumResults() << ")"; 374 } 375 376 SmallVector<AffineExpr> redDims; 377 linalgOp.getReductionDims(redDims); 378 379 // Simplifying assumption: either full tensor or full buffer mode. 380 // This allows simpler verification of output operands vs result types 381 // without premature tracking of which operand is what in mixed-mode. 382 // TODO: relax when mixed-mode needs to pass verification. 383 if (!linalgOp.getOutputBufferOperands().empty() && 384 !linalgOp.getOutputTensorOperands().empty()) 385 return op->emitOpError( 386 "expected output operands to all have tensor type or " 387 "all have buffer type"); 388 389 for (OpOperand *opOperand : linalgOp.getOutputTensorOperands()) { 390 // TODO: Enforce one output tensor per result? 391 if (opOperand->getOperandNumber() - linalgOp.getNumInputs() >= 392 linalgOp->getNumResults()) 393 continue; 394 OpResult result = linalgOp.getTiedOpResult(opOperand); 395 if (result.getType() != opOperand->get().getType()) 396 return op->emitOpError("expected type of operand #") 397 << opOperand->getOperandNumber() << " (" 398 << opOperand->get().getType() << ")" 399 << " to match type of corresponding result (" << result.getType() 400 << ")"; 401 } 402 403 // Output tensor indexing map may not depend on reduction indices. 404 for (OpOperand *opOperand : linalgOp.getOutputOperands()) { 405 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 406 for (auto expr : indexingMap.getResults()) { 407 for (auto dim : redDims) { 408 unsigned pos = dim.cast<AffineDimExpr>().getPosition(); 409 if (expr.isFunctionOfDim(pos)) { 410 std::string exprStr; 411 { 412 llvm::raw_string_ostream os(exprStr); 413 os << expr; 414 } 415 return op->emitOpError( 416 "unexpected output tensor expression in indexing map #") 417 << (opOperand->getOperandNumber() - linalgOp.getNumInputs()) 418 << " a.k.a '" << exprStr 419 << "' is function of reduction iterator 'd" << pos << "'"; 420 } 421 } 422 } 423 } 424 425 // Named ops that are defined manually have a region builder but no region at 426 // this time. Assume the region is well-formed by specification. 427 // TODO: use linalg-ods-gen for all ops when we have enough expressive power. 428 if (linalgOp->getNumRegions() == 0) { 429 assert(!linalgOp.getRegionBuilder() && "regionBuilder but no region"); 430 return success(); 431 } 432 433 auto ®ion = linalgOp->getRegion(0); 434 if (linalgOp->getNumRegions() > 1 || !llvm::hasSingleElement(region)) 435 return op->emitOpError("expected 1 region with 1 block"); 436 437 if (!linalgOp.getShapesToLoopsMap()) 438 return op->emitOpError("expected the shape-to-loops map to be non-null"); 439 440 // Simplifying assumption: bbargs match 1-1 with shape operands elemental 441 // types. 442 // TODO: once ranked shape types are plugged in, we may want to drop the 443 // corresponding bbargs, that can never be read from. This will be subject to 444 // consistency discussions (i.e. what to do with output tensors whose bbarg is 445 // not used). 446 Block &block = linalgOp->getRegion(0).front(); 447 unsigned numBBIvs = linalgOp.getNumPayloadInductionVariables(); 448 449 if (linalgOp.getNumInputsAndOutputs() + numBBIvs != block.getNumArguments()) 450 return op->emitOpError("expected as many non-induction variable region " 451 "arguments as the number of shaped operands"); 452 453 // Note: the number and type of yield values are checked in the YieldOp. 454 for (unsigned i = 0; i < numBBIvs; ++i) 455 if (!block.getArgument(i).getType().isIndex()) 456 return op->emitOpError("expected index block argument #") << i; 457 458 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 459 Type elementType = getElementTypeOrSelf(opOperand->get().getType()); 460 Type argType = 461 block.getArgument(numBBIvs + opOperand->getOperandNumber()).getType(); 462 if (elementType != argType) 463 return op->emitOpError("expected type of bb argument #") 464 << numBBIvs + opOperand->getOperandNumber() << " (" << argType 465 << ")" 466 << " to match element type of corresponding shaped operand (" 467 << elementType << ")"; 468 } 469 470 // Check if given shapes match to inferred shapes. 471 Optional<SmallVector<int64_t, 4>> endLoopRangeValues = 472 linalgOp.getStaticLoopRanges(); 473 if (!endLoopRangeValues) 474 return op->emitOpError("unable to find loop range for operation"); 475 SmallVector<int64_t, 4> startLoopRangeValues((*endLoopRangeValues).size(), 0); 476 477 // Verify only static cases since we can't get exact dimension sizes and loop 478 // ranges for dynamic cases in this stage. 479 if (llvm::none_of(*endLoopRangeValues, ShapedType::isDynamic)) { 480 for (int64_t &range : *endLoopRangeValues) 481 range -= 1; 482 for (OpOperand *opOperand : linalgOp.getInputAndOutputOperands()) { 483 AffineMap indexingMap = linalgOp.getTiedIndexingMap(opOperand); 484 SmallVector<int64_t, 4> startIndices = 485 indexingMap.compose(startLoopRangeValues); 486 SmallVector<int64_t, 4> endIndices = 487 indexingMap.compose(*endLoopRangeValues); 488 ArrayRef<int64_t> shape = linalgOp.getShape(opOperand); 489 for (auto dim : llvm::seq<int64_t>(0, shape.size())) { 490 // Ignore dynamic dimension or the case that the dimension size is 0 491 if (ShapedType::isDynamic(shape[dim]) || shape[dim] == 0) 492 continue; 493 494 // The first index or last index should be the maximum or the minimum in 495 // the inferred index ranges since the range is increasing or 496 // decreasing. The size of dimensions of shaped operands and the maximum 497 // value + 1 in the inferred range should be the same. But, for now we 498 // check if the inferred ranges are in boundary of shaped operands' size 499 // or not in case that Affine Expressions are complicated such as d0 * 3 500 // + d1 since it is not easy to handle the issues. 501 // Found the case that this solution can't check, for example, (d0, d1) 502 // -> (d1 - d0) 503 int64_t inferredDimSize = 504 std::max(startIndices[dim], endIndices[dim]) + 1; 505 if (std::min(startIndices[dim], endIndices[dim]) < 0) { 506 std::string mapStr; 507 { 508 llvm::raw_string_ostream os(mapStr); 509 os << indexingMap; 510 } 511 return op->emitOpError( 512 "unexpected result less than 0 at expression #") 513 << dim << " in " << mapStr; 514 } 515 if (indexingMap.getResult(dim).dyn_cast<AffineDimExpr>()) { 516 if (inferredDimSize != shape[dim]) { 517 return op->emitOpError("inferred shaped operand #") 518 << opOperand->getOperandNumber() 519 << " has shape's dimension #" << dim << " to be " 520 << inferredDimSize << ", but found " << shape[dim]; 521 } 522 } else { 523 if (inferredDimSize > shape[dim]) { 524 return op->emitOpError("inferred shaped operand #") 525 << opOperand->getOperandNumber() 526 << " has shape's dimension #" << dim 527 << " to be greater than or equal to " << inferredDimSize 528 << ", but found " << shape[dim]; 529 } 530 } 531 } 532 } 533 } 534 535 return success(); 536 } 537