MLIR  21.0.0git
EraseUnusedOperandsAndResults.cpp
Go to the documentation of this file.
1 //===- EraseUnusedOperandsAndResults.cpp ----------------------------------===//
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 
10 
12 
13 using namespace mlir;
14 using namespace mlir::linalg;
15 
16 /// Return `true` if the `result` of an operation `genericOp` is dead.
17 static bool isResultValueDead(linalg::GenericOp genericOp, OpResult result) {
18  if (!result.use_empty())
19  return false;
20  // If out operand not used in payload, we can drop it.
21  OpOperand *outputOpOperand =
22  genericOp.getDpsInitOperand(result.getResultNumber());
23  if (!genericOp.payloadUsesValueFromOperand(outputOpOperand))
24  return true;
25 
26  // The out operand that is part of a payload can be dropped if
27  // these conditions are met:
28  // - Result from out operand is dead.
29  // - User of arg is yield.
30  // - outArg data is not being used by other outArgs.
31 
32  // Check block arg and cycle from out operand has a single use.
33  BlockArgument outputArg =
34  genericOp.getRegionOutputArgs()[result.getResultNumber()];
35  if (!outputArg.hasOneUse())
36  return false;
37  Operation *argUserOp = *outputArg.user_begin();
38 
39  // Check argUser has no other use.
40  if (!argUserOp->use_empty())
41  return false;
42 
43  // Check that argUser is a yield.
44  auto yieldOp = dyn_cast<linalg::YieldOp>(argUserOp);
45  if (!yieldOp)
46  return false;
47 
48  // Check outArg data is not being used by other outArgs.
49  if (yieldOp.getOperand(result.getResultNumber()) != outputArg)
50  return false;
51 
52  return true;
53 }
54 
55 //===---------------------------------------------------------------------===//
56 // Helper methods for operand deduplication and dead results elimination
57 //===---------------------------------------------------------------------===//
58 
59 // Deduplicate input operands, and return the
60 // - Mapping from operand position in the original op, to operand position in
61 // the canonicalized op.
62 // - The preserved input operands list (by reference).
63 llvm::SmallDenseMap<unsigned, unsigned> static deduplicateInputOperands(
64  GenericOp genericOp, SmallVector<OpOperand *> &droppedOpOperands,
65  SmallVector<Value> &newInputOperands,
66  SmallVector<AffineMap> &newIndexingMaps) {
67  llvm::SmallDenseMap<unsigned, unsigned> origToNewPos;
68  llvm::SmallDenseMap<std::pair<Value, AffineMap>, unsigned> dedupedInputs;
69  for (const auto &en : llvm::enumerate(genericOp.getDpsInputOperands())) {
70  OpOperand *inputOpOperand = en.value();
71  // Check if operand is dead and if dropping the indexing map makes the
72  // loops to shape computation invalid.
73  if (!genericOp.payloadUsesValueFromOperand(inputOpOperand)) {
74  // Add the current operands to the list of potentially droppable
75  // operands. If it cannot be dropped, this needs to be popped back.
76  droppedOpOperands.push_back(inputOpOperand);
77  if (genericOp.canOpOperandsBeDropped(droppedOpOperands))
78  continue;
79  droppedOpOperands.pop_back();
80  }
81 
82  // Check if this operand is a duplicate.
83  AffineMap indexingMap = genericOp.getMatchingIndexingMap(inputOpOperand);
84  auto it =
85  dedupedInputs.find(std::make_pair(inputOpOperand->get(), indexingMap));
86  if (it != dedupedInputs.end()) {
87  origToNewPos[en.index()] = it->second;
88  droppedOpOperands.push_back(inputOpOperand);
89  continue;
90  }
91 
92  // This is a preserved argument.
93  origToNewPos[en.index()] = newInputOperands.size();
94  dedupedInputs[{inputOpOperand->get(), indexingMap}] =
95  newInputOperands.size();
96  newInputOperands.push_back(inputOpOperand->get());
97  newIndexingMaps.push_back(indexingMap);
98  }
99  return origToNewPos;
100 }
101 
102 // Deduplicate output operands, and return the
103 // - Mapping from operand position in the original op, to operand position in
104 // the canonicalized op.
105 // - The preserved output operands list (by reference).
106 llvm::SmallDenseMap<unsigned, unsigned> static deduplicateOutputOperands(
107  GenericOp genericOp, SmallVector<OpOperand *> &droppedOpOperands,
108  SmallVector<Value> &newOutputOperands,
109  SmallVector<AffineMap> &newIndexingMaps, bool removeOutputs) {
110  llvm::SmallDenseMap<unsigned, unsigned> origToNewPos;
111  llvm::SmallDenseMap<std::tuple<Value, AffineMap, Value>, unsigned>
112  dedupedOutpts;
113  // If the op doesn't have tensor semantics or outputs should not be removed,
114  // keep all the outputs as preserved.
115  if (!genericOp.hasPureTensorSemantics() || !removeOutputs) {
116  for (const auto &en : llvm::enumerate(genericOp.getDpsInitsMutable())) {
117  origToNewPos[en.index()] = newOutputOperands.size();
118  newOutputOperands.push_back(en.value().get());
119  newIndexingMaps.push_back(genericOp.getMatchingIndexingMap(&en.value()));
120  }
121  return origToNewPos;
122  }
123  // Output argument can be dropped if the result has
124  // - no users, and
125  // - it is not used in the payload, and
126  // - the corresponding indexing maps are not needed for loop bound
127  // computation.
128  auto yieldOp = cast<YieldOp>(genericOp.getBody()->getTerminator());
129  for (const auto &outputOpOperand :
130  llvm::enumerate(genericOp.getDpsInitsMutable())) {
131  OpResult result = genericOp.getTiedOpResult(&outputOpOperand.value());
132  AffineMap indexingMap =
133  genericOp.getMatchingIndexingMap(&outputOpOperand.value());
134  auto key = std::make_tuple(outputOpOperand.value().get(), indexingMap,
135  yieldOp->getOperand(outputOpOperand.index()));
136  if (isResultValueDead(genericOp, result)) {
137  // Check if the opoperand can be dropped without affecting loop
138  // bound computation. Add the operand to the list of dropped op
139  // operand for checking. If it cannot be dropped, need to pop the
140  // value back.
141  droppedOpOperands.push_back(&outputOpOperand.value());
142  if (genericOp.canOpOperandsBeDropped(droppedOpOperands)) {
143  continue;
144  }
145  droppedOpOperands.pop_back();
146  }
147 
148  if (!genericOp.payloadUsesValueFromOperand(&outputOpOperand.value())) {
149  // The out operand can also be dropped if it is computed redundantly
150  // by another result, the conditions for that are
151  // - The same operand is used as the out operand
152  // - The same indexing map is used
153  // - The same yield value is used.
154  auto it = dedupedOutpts.find(key);
155  if (it != dedupedOutpts.end()) {
156  origToNewPos[outputOpOperand.index()] = it->second;
157  droppedOpOperands.push_back(&outputOpOperand.value());
158  continue;
159  }
160  }
161 
162  origToNewPos[outputOpOperand.index()] = newOutputOperands.size();
163  dedupedOutpts[key] = newOutputOperands.size();
164  newOutputOperands.push_back(outputOpOperand.value().get());
165  newIndexingMaps.push_back(
166  genericOp.getMatchingIndexingMap(&outputOpOperand.value()));
167  }
168  return origToNewPos;
169 }
170 
171 // Populate the body of the canonicalized operation.
172 static void populateOpPayload(
173  GenericOp genericOp, GenericOp newOp,
174  const llvm::SmallDenseMap<unsigned, unsigned> &origInsToNewInsPos,
175  const llvm::SmallDenseMap<unsigned, unsigned> &origOutsToNewOutsPos,
176  RewriterBase &rewriter) {
177  // Merge the body of the original op with the new op.
178  Block *newOpBlock = &newOp.getRegion().front();
179  assert(newOpBlock->empty() && "expected new op to have an empty payload");
180  Block *origOpBlock = &genericOp.getRegion().front();
181  SmallVector<Value> replacements(origOpBlock->getNumArguments(), nullptr);
182 
183  // Replace all arguments in the original op, with arguments from the
184  // canonicalized op.
185  auto updateReplacements =
186  [&](SmallVector<OpOperand *> &origOperands,
187  SmallVector<OpOperand *> &newOperands,
188  const llvm::SmallDenseMap<unsigned, unsigned> &map) {
189  for (const auto &origOperand : llvm::enumerate(origOperands)) {
190  auto it = map.find(origOperand.index());
191  if (it == map.end())
192  continue;
193  OpOperand *newOperand = newOperands[it->second];
194  replacements[origOperand.value()->getOperandNumber()] =
195  newOpBlock->getArgument(newOperand->getOperandNumber());
196  }
197  };
198 
199  SmallVector<OpOperand *> origInputOperands = genericOp.getDpsInputOperands();
200  SmallVector<OpOperand *> newInputOperands = newOp.getDpsInputOperands();
201  updateReplacements(origInputOperands, newInputOperands, origInsToNewInsPos);
202 
203  SmallVector<OpOperand *> origOutputOperands = llvm::to_vector(llvm::map_range(
204  genericOp.getDpsInitsMutable(), [](OpOperand &o) { return &o; }));
205  SmallVector<OpOperand *> newOutputOperands = llvm::to_vector(llvm::map_range(
206  newOp.getDpsInitsMutable(), [](OpOperand &o) { return &o; }));
207  updateReplacements(origOutputOperands, newOutputOperands,
208  origOutsToNewOutsPos);
209 
210  // Drop the unused yield args.
211  if (newOp.getNumDpsInits() != genericOp.getNumDpsInits()) {
212  OpBuilder::InsertionGuard g(rewriter);
213  YieldOp origYieldOp = cast<YieldOp>(origOpBlock->getTerminator());
214  rewriter.setInsertionPoint(origYieldOp);
215 
216  SmallVector<Value> newYieldVals(newOp.getNumDpsInits(), nullptr);
217  for (const auto &yieldOpOperands :
218  llvm::enumerate(origYieldOp.getValues())) {
219  auto it = origOutsToNewOutsPos.find(yieldOpOperands.index());
220  if (it == origOutsToNewOutsPos.end())
221  continue;
222  newYieldVals[it->second] = yieldOpOperands.value();
223  }
224  rewriter.replaceOpWithNewOp<YieldOp>(origYieldOp, newYieldVals);
225  }
226 
227  rewriter.mergeBlocks(origOpBlock, newOpBlock, replacements);
228 }
229 
230 FailureOr<linalg::GenericOp>
232  RewriterBase &rewriter, linalg::GenericOp genericOp, bool removeOutputs) {
233  // Create a map from argument position in the original op to the argument
234  // position in the new op. If the argument is dropped it wont have an entry.
235  SmallVector<OpOperand *> droppedOpOperands;
236 
237  // Information needed to build the new op.
238  SmallVector<Value> newInputOperands, newOutputOperands;
239  SmallVector<AffineMap> newIndexingMaps;
240 
241  // Gather information about duplicate input operands.
242  llvm::SmallDenseMap<unsigned, unsigned> origInsToNewInsPos =
243  deduplicateInputOperands(genericOp, droppedOpOperands, newInputOperands,
244  newIndexingMaps);
245 
246  // Gather information about the dropped outputs.
247  llvm::SmallDenseMap<unsigned, unsigned> origOutsToNewOutsPos =
248  deduplicateOutputOperands(genericOp, droppedOpOperands, newOutputOperands,
249  newIndexingMaps, removeOutputs);
250 
251  // Check if there is any change to operands.
252  if (newInputOperands.size() + newOutputOperands.size() ==
253  genericOp->getNumOperands())
254  return genericOp;
255 
256  // Create the new op with the body being empty.
257  Location loc = genericOp.getLoc();
258  SmallVector<Type> newResultTypes;
259  for (Value v : newOutputOperands)
260  if (isa<TensorType>(v.getType()))
261  newResultTypes.push_back(v.getType());
262  auto newOp = rewriter.create<GenericOp>(
263  loc, newResultTypes, newInputOperands, newOutputOperands,
264  rewriter.getAffineMapArrayAttr(newIndexingMaps),
265  genericOp.getIteratorTypes(), genericOp.getDocAttr(),
266  genericOp.getLibraryCallAttr(),
267  [](OpBuilder & /*builder*/, Location /*loc*/, ValueRange /*args*/) {
268  return;
269  });
270  // Copy over unknown attributes. They might be load bearing for some flow.
271  ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames();
272  for (NamedAttribute kv : genericOp->getAttrs())
273  if (!llvm::is_contained(odsAttrs, kv.getName().getValue()))
274  newOp->setAttr(kv.getName(), kv.getValue());
275 
276  // Fix up the payload of the canonicalized operation.
277  populateOpPayload(genericOp, newOp, origInsToNewInsPos, origOutsToNewOutsPos,
278  rewriter);
279 
280  // Replace all live uses of the op.
281  SmallVector<Value> replacementsVals(genericOp->getNumResults(), nullptr);
282  for (const auto &result : llvm::enumerate(genericOp.getResults())) {
283  auto it = origOutsToNewOutsPos.find(result.index());
284  if (it == origOutsToNewOutsPos.end())
285  continue;
286  replacementsVals[result.index()] = newOp.getResult(it->second);
287  }
288  rewriter.replaceOp(genericOp, replacementsVals);
289  return newOp;
290 }
291 
292 namespace {
293 
294 struct DeduplicateAndRemoveDeadOperandsAndResults
295  : public OpRewritePattern<GenericOp> {
296  DeduplicateAndRemoveDeadOperandsAndResults(MLIRContext *ctx,
297  bool removeOutputs)
298  : OpRewritePattern<GenericOp>(ctx), removeOutputs(removeOutputs) {}
299 
300  LogicalResult matchAndRewrite(GenericOp genericOp,
301  PatternRewriter &rewriter) const override {
302  FailureOr<GenericOp> newOp = deduplicateOperandsAndRemoveDeadResults(
303  rewriter, genericOp, removeOutputs);
304  if (failed(newOp) || newOp.value() == genericOp) {
305  return rewriter.notifyMatchFailure(
306  genericOp, "failed to dedup operands/remove dead results");
307  }
308  return success();
309  }
310 
311 private:
312  /// If unset, outputs are not modified by this pattern.
313  bool removeOutputs;
314 };
315 
316 /// Remove unused cycles.
317 /// We can remove unused cycle within a payload of generic region
318 /// if these conditions are met:
319 /// - Result from out operand is dead.
320 /// - Block arg from out operand has a single use in the %cycle
321 /// instruction.
322 /// - Cycle has a single use and it is in yield.
323 struct RemoveUnusedCycleInGenericOp : public OpRewritePattern<GenericOp> {
325 
326  LogicalResult matchAndRewrite(GenericOp genericOp,
327  PatternRewriter &rewriter) const override {
328 
329  // If the op doesnt have tensor semantics, preserve the outputs as is.
330  if (!genericOp.hasPureTensorSemantics())
331  return failure();
332 
333  bool hasRemovedCycles = false;
334  // Iterate over output operands and remove any unused cycles.
335  for (const auto &outputOpOperand :
336  llvm::enumerate(genericOp.getDpsInits())) {
337 
338  // Check that result from out operand is dead.
339  Value result = genericOp.getResult(outputOpOperand.index());
340  if (!result.use_empty())
341  continue;
342 
343  // Check that outputArg has one use in cycle.
344  BlockArgument outputArg =
345  genericOp.getRegionOutputArgs()[outputOpOperand.index()];
346  if (!outputArg.hasOneUse())
347  continue;
348 
349  // Check cycle has at most one use.
350  Operation *cycleOp = *outputArg.user_begin();
351  if (!cycleOp->hasOneUse())
352  continue;
353 
354  // Check that the cycleUser is a yield.
355  Operation *cycleUserOp = *cycleOp->user_begin();
356  if (!isa<linalg::YieldOp>(cycleUserOp))
357  continue;
358 
359  // Check that argIndex matches yieldIndex, else data is being used.
360  if (cycleUserOp->getOperand(outputOpOperand.index()) !=
361  cycleOp->getResult(0))
362  continue;
363 
364  // Directly replace the cycle with the blockArg such that
365  // Deduplicate pattern can eliminate it along with unused yield.
366  rewriter.replaceOp(cycleOp, outputArg);
367  rewriter.modifyOpInPlace(genericOp, [] {});
368  hasRemovedCycles = true;
369  }
370 
371  if (hasRemovedCycles) {
372  return success();
373  }
374 
375  return failure();
376  }
377 };
378 
379 /// Fold uses of duplicate inputs in the body of a linalg.generic. E.g.:
380 /// ```
381 /// linalg.generic ins(%a, %b, %a, %b) outs(%a)
382 /// ^bb0(%in0, %in1, %in2, %in3, %out1)
383 /// ```
384 /// Assuming that all %a and %b have the same index map:
385 /// * All uses of %in0 and %in2 are replaced with %out1
386 /// * All uses of %in1 are replaced with %in3
387 /// This pattern can enable additional canonicalizations: In the above example,
388 /// %in0, %in1 and %in3 have no uses anymore and their corresponding operands
389 /// can be folded away. This pattern does not modify uses of output block args.
390 struct FoldDuplicateInputBbArgs : public OpRewritePattern<GenericOp> {
392 
393  LogicalResult matchAndRewrite(GenericOp genericOp,
394  PatternRewriter &rewriter) const override {
395  // Find replacement bbArgs for all input bbArg.
396  DenseMap<int, int> replacements;
397  for (int i = 0; i < genericOp.getNumDpsInputs(); ++i) {
398  // Skip bbArgs that have no uses.
399  if (genericOp.getBody()->getArgument(i).getUses().empty())
400  continue;
401  // Find replacement bbArg. This can be an input or an output bbArg.
402  for (int j = genericOp->getNumOperands() - 1; j > i; --j) {
403  if (genericOp->getOperand(i) == genericOp->getOperand(j) &&
404  genericOp.getIndexingMapsArray()[i] ==
405  genericOp.getIndexingMapsArray()[j]) {
406  replacements[i] = j;
407  break;
408  }
409  }
410  }
411 
412  // Stop here if no replacements were found.
413  if (replacements.empty())
414  return failure();
415 
416  // Rewrite the op.
417  rewriter.modifyOpInPlace(genericOp, [&]() {
418  for (auto [before, after] : replacements) {
419  BlockArgument bbArg = genericOp.getBody()->getArgument(before);
420  BlockArgument replacement = genericOp.getBody()->getArgument(after);
421  rewriter.replaceAllUsesWith(bbArg, replacement);
422  }
423  });
424 
425  return success();
426  }
427 };
428 
429 } // namespace
430 
433  patterns.insert<DeduplicateAndRemoveDeadOperandsAndResults>(
434  patterns.getContext(), /*removeOutputs=*/true);
435  patterns.insert<RemoveUnusedCycleInGenericOp>(patterns.getContext());
436 }
437 
440  patterns.insert<DeduplicateAndRemoveDeadOperandsAndResults>(
441  patterns.getContext(), /*removeOutputs=*/false);
442  patterns.insert<FoldDuplicateInputBbArgs>(patterns.getContext());
443 }
static llvm::SmallDenseMap< unsigned, unsigned > deduplicateOutputOperands(GenericOp genericOp, SmallVector< OpOperand * > &droppedOpOperands, SmallVector< Value > &newOutputOperands, SmallVector< AffineMap > &newIndexingMaps, bool removeOutputs)
static llvm::SmallDenseMap< unsigned, unsigned > deduplicateInputOperands(GenericOp genericOp, SmallVector< OpOperand * > &droppedOpOperands, SmallVector< Value > &newInputOperands, SmallVector< AffineMap > &newIndexingMaps)
static void populateOpPayload(GenericOp genericOp, GenericOp newOp, const llvm::SmallDenseMap< unsigned, unsigned > &origInsToNewInsPos, const llvm::SmallDenseMap< unsigned, unsigned > &origOutsToNewOutsPos, RewriterBase &rewriter)
static bool isResultValueDead(linalg::GenericOp genericOp, OpResult result)
Return true if the result of an operation genericOp is dead.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition: AffineMap.h:46
This class represents an argument of a Block.
Definition: Value.h:319
Block represents an ordered list of Operations.
Definition: Block.h:33
bool empty()
Definition: Block.h:148
BlockArgument getArgument(unsigned i)
Definition: Block.h:129
unsigned getNumArguments()
Definition: Block.h:128
Operation * getTerminator()
Get the terminator operation of this block.
Definition: Block.cpp:246
Operation & front()
Definition: Block.h:153
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition: Builders.cpp:314
IRValueT get() const
Return the current value being used by this operand.
Definition: UseDefLists.h:160
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:66
MLIRContext is the top-level object for a collection of MLIR operations.
Definition: MLIRContext.h:60
NamedAttribute represents a combination of a name and an Attribute value.
Definition: Attributes.h:207
RAII guard to reset the insertion point of the builder when destroyed.
Definition: Builders.h:346
This class helps build Operations.
Definition: Builders.h:205
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition: Builders.h:396
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition: Builders.cpp:453
This class represents an operand of an operation.
Definition: Value.h:267
unsigned getOperandNumber()
Return which operand this is in the OpOperand list of the Operation.
Definition: Value.cpp:216
This is a value defined by a result of an operation.
Definition: Value.h:457
unsigned getResultNumber() const
Returns the number of this result.
Definition: Value.h:469
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
bool use_empty()
Returns true if this operation has no uses.
Definition: Operation.h:853
Value getOperand(unsigned idx)
Definition: Operation.h:350
bool hasOneUse()
Returns true if this operation has exactly one use.
Definition: Operation.h:850
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition: Operation.h:407
user_iterator user_begin()
Definition: Operation.h:870
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Definition: PatternMatch.h:791
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
Definition: PatternMatch.h:400
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
Definition: PatternMatch.h:724
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
Definition: PatternMatch.h:644
void mergeBlocks(Block *source, Block *dest, ValueRange argValues=std::nullopt)
Inline the operations of block 'source' into the end of block 'dest'.
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
Definition: PatternMatch.h:636
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Definition: PatternMatch.h:542
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:381
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition: Value.h:218
user_iterator user_begin() const
Definition: Value.h:226
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition: Value.h:215
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition: Matchers.h:344
FailureOr< linalg::GenericOp > deduplicateOperandsAndRemoveDeadResults(RewriterBase &rewriter, linalg::GenericOp genericOp, bool removeOutputs)
Method to deduplicate operands and remove dead results of linalg.generic operations.
void populateEraseUnusedOperandsAndResultsPatterns(RewritePatternSet &patterns)
Pattern to remove dead operands and results of linalg.generic operations.
void populateEraseUnnecessaryInputsPatterns(RewritePatternSet &patterns)
Patterns to promote inputs to outputs and remove unused inputs of linalg.generic ops.
Include the generated interface declarations.
const FrozenRewritePatternSet & patterns
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Definition: PatternMatch.h:358
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.