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 &region = 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