MLIR 24.0.0git
BufferizableOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- BufferizableOpInterfaceImpl.cpp - Impl. of BufferizableOpInterface -===//
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
19#include "mlir/IR/Dialect.h"
20#include "mlir/IR/Operation.h"
22#include "llvm/ADT/SmallVectorExtras.h"
23
24using namespace mlir;
25using namespace mlir::bufferization;
26using namespace mlir::scf;
27
28namespace mlir {
29namespace scf {
30namespace {
31
32/// Helper function for loop bufferization. Cast the given buffer to the given
33/// memref type.
34static Value castBuffer(OpBuilder &b, Value buffer, Type type,
35 const BufferizationOptions &options) {
36 // If the buffer already has the correct type, no cast is needed.
37 if (buffer.getType() == type)
38 return buffer;
39
40 return *options.castFn(b, buffer.getLoc(), type, buffer);
41}
42
43/// Helper function for loop bufferization. Return "true" if the given value
44/// is guaranteed to not alias with an external tensor apart from values in
45/// `exceptions`. A value is external if it is defined outside of the given
46/// region or if it is an entry block argument of the region.
47static bool doesNotAliasExternalValue(Value value, Region *region,
48 ValueRange exceptions,
49 const OneShotAnalysisState &state) {
50 assert(region->hasOneBlock() && "expected region with single block");
51 bool result = true;
52 state.applyOnAliases(value, [&](Value alias) {
53 if (llvm::is_contained(exceptions, alias))
54 return;
55 Region *aliasRegion = alias.getParentRegion();
56 if (isa<BlockArgument>(alias) && !region->isProperAncestor(aliasRegion))
57 result = false;
58 if (isa<OpResult>(alias) && !region->isAncestor(aliasRegion))
59 result = false;
60 });
61 return result;
62}
63
64/// Bufferization of scf.condition.
65struct ConditionOpInterface
66 : public BufferizableOpInterface::ExternalModel<ConditionOpInterface,
67 scf::ConditionOp> {
68 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
69 const AnalysisState &state) const {
70 return true;
71 }
72
73 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
74 const AnalysisState &state) const {
75 return false;
76 }
77
78 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
79 const AnalysisState &state) const {
80 return {};
81 }
82
83 bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand,
84 const AnalysisState &state) const {
85 // Condition operands always bufferize inplace. Otherwise, an alloc + copy
86 // may be generated inside the block. We should not return/yield allocations
87 // when possible.
88 return true;
89 }
90
91 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
92 const BufferizationOptions &options,
93 BufferizationState &state) const {
94 auto conditionOp = cast<scf::ConditionOp>(op);
95 auto whileOp = cast<scf::WhileOp>(conditionOp->getParentOp());
96
97 SmallVector<Value> newArgs;
98 for (const auto &it : llvm::enumerate(conditionOp.getArgs())) {
99 Value value = it.value();
100 if (isa<TensorLikeType>(value.getType())) {
101 FailureOr<Value> maybeBuffer =
102 getBuffer(rewriter, value, options, state);
103 if (failed(maybeBuffer))
104 return failure();
105 FailureOr<BufferLikeType> resultType = bufferization::getBufferType(
106 whileOp.getAfterArguments()[it.index()], options, state);
107 if (failed(resultType))
108 return failure();
109 Value buffer = castBuffer(rewriter, *maybeBuffer, *resultType, options);
110 newArgs.push_back(buffer);
111 } else {
112 newArgs.push_back(value);
113 }
114 }
115
116 replaceOpWithNewBufferizedOp<scf::ConditionOp>(
117 rewriter, op, conditionOp.getCondition(), newArgs);
118 return success();
119 }
120};
121
122/// Return the unique scf.yield op. If there are multiple or no scf.yield ops,
123/// return an empty op.
124static scf::YieldOp getUniqueYieldOp(scf::ExecuteRegionOp executeRegionOp) {
125 scf::YieldOp result;
126 for (Block &block : executeRegionOp.getRegion()) {
127 if (auto yieldOp = dyn_cast<scf::YieldOp>(block.getTerminator())) {
128 if (result)
129 return {};
130 result = yieldOp;
131 }
132 }
133 return result;
134}
135
136/// Bufferization of scf.execute_region. Can be analyzed, but bufferization not
137/// fully implemented at the moment.
138struct ExecuteRegionOpInterface
139 : public OpWithUnstructuredControlFlowBufferizableOpInterfaceExternalModel<
140 ExecuteRegionOpInterface, scf::ExecuteRegionOp> {
141
142 static bool supportsUnstructuredControlFlow() { return true; }
143
144 bool isWritable(Operation *op, Value value,
145 const AnalysisState &state) const {
146 return true;
147 }
148
149 LogicalResult verifyAnalysis(Operation *op,
150 const AnalysisState &state) const {
151 auto executeRegionOp = cast<scf::ExecuteRegionOp>(op);
152 // TODO: scf.execute_region with multiple yields are not supported.
153 if (!getUniqueYieldOp(executeRegionOp))
154 return op->emitOpError("op without unique scf.yield is not supported");
155 return success();
156 }
157
158 AliasingOpOperandList
159 getAliasingOpOperands(Operation *op, Value value,
160 const AnalysisState &state) const {
161 if (auto bbArg = dyn_cast<BlockArgument>(value))
162 return getAliasingBranchOpOperands(op, bbArg, state);
163
164 // ExecuteRegionOps do not have tensor OpOperands. The yielded value can be
165 // any SSA value that is in scope. To allow for use-def chain traversal
166 // through ExecuteRegionOps in the analysis, the corresponding yield value
167 // is considered to be aliasing with the result.
168 auto executeRegionOp = cast<scf::ExecuteRegionOp>(op);
169 auto it = llvm::find(op->getOpResults(), value);
170 assert(it != op->getOpResults().end() && "invalid value");
171 size_t resultNum = std::distance(op->getOpResults().begin(), it);
172 auto yieldOp = getUniqueYieldOp(executeRegionOp);
173 // Note: If there is no unique scf.yield op, `verifyAnalysis` will fail.
174 if (!yieldOp)
175 return {};
176 return {{&yieldOp->getOpOperand(resultNum), BufferRelation::Equivalent}};
177 }
178
179 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
180 const BufferizationOptions &options,
181 BufferizationState &state) const {
182 auto executeRegionOp = cast<scf::ExecuteRegionOp>(op);
183 auto yieldOp = getUniqueYieldOp(executeRegionOp);
184 TypeRange newResultTypes(yieldOp.getResults());
185
186 // Create new op and move over region.
187 auto newOp = scf::ExecuteRegionOp::create(
188 rewriter, op->getLoc(), newResultTypes, executeRegionOp.getNoInline());
189 newOp.getRegion().takeBody(executeRegionOp.getRegion());
190
191 // Bufferize every block.
192 for (Block &block : newOp.getRegion())
194 options, state)))
195 return failure();
196
197 // Update all uses of the old op.
198 rewriter.setInsertionPointAfter(newOp);
199 SmallVector<Value> newResults;
200 for (const auto &it : llvm::enumerate(executeRegionOp->getResultTypes())) {
201 if (isa<TensorLikeType>(it.value())) {
202 newResults.push_back(bufferization::ToTensorOp::create(
203 rewriter, executeRegionOp.getLoc(), it.value(),
204 newOp->getResult(it.index())));
205 } else {
206 newResults.push_back(newOp->getResult(it.index()));
207 }
208 }
209
210 // Replace old op.
211 rewriter.replaceOp(executeRegionOp, newResults);
212
213 return success();
214 }
215};
216
217/// Bufferization of scf.if. Replace with a new scf.if that yields memrefs.
218struct IfOpInterface
219 : public BufferizableOpInterface::ExternalModel<IfOpInterface, scf::IfOp> {
220 AliasingOpOperandList
221 getAliasingOpOperands(Operation *op, Value value,
222 const AnalysisState &state) const {
223 // IfOps do not have tensor OpOperands. The yielded value can be any SSA
224 // value that is in scope. To allow for use-def chain traversal through
225 // IfOps in the analysis, both corresponding yield values from the then/else
226 // branches are considered to be aliasing with the result.
227 auto ifOp = cast<scf::IfOp>(op);
228 size_t resultNum = std::distance(op->getOpResults().begin(),
229 llvm::find(op->getOpResults(), value));
230 OpOperand *thenOperand = &ifOp.thenYield()->getOpOperand(resultNum);
231 OpOperand *elseOperand = &ifOp.elseYield()->getOpOperand(resultNum);
232 return {{thenOperand, BufferRelation::Equivalent, /*isDefinite=*/false},
233 {elseOperand, BufferRelation::Equivalent, /*isDefinite=*/false}};
234 }
235
236 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
237 const BufferizationOptions &options,
238 BufferizationState &state) const {
239 OpBuilder::InsertionGuard g(rewriter);
240 auto ifOp = cast<scf::IfOp>(op);
241
242 // Compute bufferized result types.
243 SmallVector<Type> newTypes;
244 for (Value result : ifOp.getResults()) {
245 if (!isa<TensorLikeType>(result.getType())) {
246 newTypes.push_back(result.getType());
247 continue;
248 }
249 auto bufferType = bufferization::getBufferType(result, options, state);
250 if (failed(bufferType))
251 return failure();
252 newTypes.push_back(*bufferType);
253 }
254
255 // Create new op.
256 rewriter.setInsertionPoint(ifOp);
257 auto newIfOp = scf::IfOp::create(rewriter, ifOp.getLoc(), newTypes,
258 ifOp.getCondition(),
259 /*withElseRegion=*/true);
260
261 // Move over then/else blocks.
262 rewriter.mergeBlocks(ifOp.thenBlock(), newIfOp.thenBlock());
263 rewriter.mergeBlocks(ifOp.elseBlock(), newIfOp.elseBlock());
264
265 // Replace op results.
266 replaceOpWithBufferizedValues(rewriter, op, newIfOp->getResults());
267
268 return success();
269 }
270
271 FailureOr<BufferLikeType>
272 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
273 const BufferizationState &state,
274 SmallVector<Value> &invocationStack) const {
275 auto ifOp = cast<scf::IfOp>(op);
276 auto thenYieldOp = cast<scf::YieldOp>(ifOp.thenBlock()->getTerminator());
277 auto elseYieldOp = cast<scf::YieldOp>(ifOp.elseBlock()->getTerminator());
278 assert(value.getDefiningOp() == op && "invalid valid");
279
280 // Determine buffer types of the true/false branches.
281 auto opResult = cast<OpResult>(value);
282 auto thenValue = thenYieldOp.getOperand(opResult.getResultNumber());
283 auto elseValue = elseYieldOp.getOperand(opResult.getResultNumber());
284 BufferLikeType thenBufferType, elseBufferType;
285 if (isa<BufferLikeType>(thenValue.getType())) {
286 // True branch was already bufferized.
287 thenBufferType = cast<BufferLikeType>(thenValue.getType());
288 } else {
289 auto maybeBufferType = bufferization::getBufferType(
290 thenValue, options, state, invocationStack);
291 if (failed(maybeBufferType))
292 return failure();
293 thenBufferType = *maybeBufferType;
294 }
295 if (isa<BufferLikeType>(elseValue.getType())) {
296 // False branch was already bufferized.
297 elseBufferType = cast<BufferLikeType>(elseValue.getType());
298 } else {
299 auto maybeBufferType = bufferization::getBufferType(
300 elseValue, options, state, invocationStack);
301 if (failed(maybeBufferType))
302 return failure();
303 elseBufferType = *maybeBufferType;
304 }
305
306 // Best case: Both branches have the exact same buffer type.
307 if (thenBufferType == elseBufferType)
308 return cast<BufferLikeType>(thenBufferType);
309
310 auto reconciled = options.reconcileBufferTypeMismatchFn(
311 thenBufferType, elseBufferType, options);
312 if (failed(reconciled))
313 return op->emitError("incompatible buffer types on then/else branches");
314
315 return *reconciled;
316 }
317};
318
319/// Bufferization of scf.index_switch. Replace with a new scf.index_switch that
320/// yields memrefs.
321struct IndexSwitchOpInterface
322 : public BufferizableOpInterface::ExternalModel<IndexSwitchOpInterface,
323 scf::IndexSwitchOp> {
324 AliasingOpOperandList
325 getAliasingOpOperands(Operation *op, Value value,
326 const AnalysisState &state) const {
327 // IndexSwitchOps do not have tensor OpOperands. The yielded value can be
328 // any SSA. This is similar to IfOps.
329 auto switchOp = cast<scf::IndexSwitchOp>(op);
330 int64_t resultNum = cast<OpResult>(value).getResultNumber();
331 AliasingOpOperandList result;
332 for (int64_t i = 0, numCases = switchOp.getNumCases(); i < numCases; ++i) {
333 auto yieldOp =
334 cast<scf::YieldOp>(switchOp.getCaseBlock(i).getTerminator());
335 result.addAlias(AliasingOpOperand(&yieldOp->getOpOperand(resultNum),
336 BufferRelation::Equivalent,
337 /*isDefinite=*/false));
338 }
339 auto defaultYieldOp =
340 cast<scf::YieldOp>(switchOp.getDefaultBlock().getTerminator());
341 result.addAlias(AliasingOpOperand(&defaultYieldOp->getOpOperand(resultNum),
342 BufferRelation::Equivalent,
343 /*isDefinite=*/false));
344 return result;
345 }
346
347 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
348 const BufferizationOptions &options,
349 BufferizationState &state) const {
350 OpBuilder::InsertionGuard g(rewriter);
351 auto switchOp = cast<scf::IndexSwitchOp>(op);
352
353 // Compute bufferized result types.
354 SmallVector<Type> newTypes;
355 for (Value result : switchOp.getResults()) {
356 if (!isa<TensorLikeType>(result.getType())) {
357 newTypes.push_back(result.getType());
358 continue;
359 }
360 auto bufferType = bufferization::getBufferType(result, options, state);
361 if (failed(bufferType))
362 return failure();
363 newTypes.push_back(*bufferType);
364 }
365
366 // Create new op.
367 rewriter.setInsertionPoint(switchOp);
368 auto newSwitchOp = scf::IndexSwitchOp::create(
369 rewriter, switchOp.getLoc(), newTypes, switchOp.getArg(),
370 switchOp.getCases(), switchOp.getCases().size());
371
372 // Move over blocks.
373 for (auto [src, dest] :
374 llvm::zip(switchOp.getCaseRegions(), newSwitchOp.getCaseRegions()))
375 rewriter.inlineRegionBefore(src, dest, dest.begin());
376 rewriter.inlineRegionBefore(switchOp.getDefaultRegion(),
377 newSwitchOp.getDefaultRegion(),
378 newSwitchOp.getDefaultRegion().begin());
379
380 // Replace op results.
381 replaceOpWithBufferizedValues(rewriter, op, newSwitchOp->getResults());
382
383 return success();
384 }
385
386 FailureOr<BufferLikeType>
387 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
388 const BufferizationState &state,
389 SmallVector<Value> &invocationStack) const {
390 auto switchOp = cast<scf::IndexSwitchOp>(op);
391 assert(value.getDefiningOp() == op && "invalid value");
392 int64_t resultNum = cast<OpResult>(value).getResultNumber();
393
394 auto getYieldedBufferType = [&](Block &b) -> FailureOr<BufferLikeType> {
395 auto yieldOp = cast<scf::YieldOp>(b.getTerminator());
396 Value yieldedValue = yieldOp->getOperand(resultNum);
397 if (auto bufferType = dyn_cast<BufferLikeType>(yieldedValue.getType()))
398 return bufferType;
399 return bufferization::getBufferType(yieldedValue, options, state,
400 invocationStack);
401 };
402
403 // Compute buffer type of the default case.
404 auto maybeBufferType = getYieldedBufferType(switchOp.getDefaultBlock());
405 if (failed(maybeBufferType))
406 return failure();
407 BufferLikeType bufferType = *maybeBufferType;
408
409 // Compute buffer types of all other cases.
410 for (int64_t i = 0, numCases = switchOp.getNumCases(); i < numCases; ++i) {
411 auto yieldedBufferType = getYieldedBufferType(switchOp.getCaseBlock(i));
412 if (failed(yieldedBufferType))
413 return failure();
414
415 // Best case: Both branches have the exact same buffer type.
416 if (bufferType == *yieldedBufferType)
417 continue;
418
419 auto reconciled = options.reconcileBufferTypeMismatchFn(
420 bufferType, *yieldedBufferType, options);
421 if (failed(reconciled))
422 return op->emitError("incompatible buffer types on switch cases");
423 bufferType = *reconciled;
424 }
425
426 return cast<BufferLikeType>(bufferType);
427 }
428};
429
430/// Helper function for loop bufferization. Return the indices of all values
431/// that have a tensor type.
432static DenseSet<int64_t> getTensorIndices(ValueRange values) {
434 for (const auto &it : llvm::enumerate(values))
435 if (isa<TensorLikeType>(it.value().getType()))
436 result.insert(it.index());
437 return result;
438}
439
440/// Helper function for loop bufferization. Return the indices of all
441/// bbArg/yielded value pairs who's buffer relation is "Equivalent".
442DenseSet<int64_t> getEquivalentBuffers(Block::BlockArgListType bbArgs,
443 ValueRange yieldedValues,
444 const AnalysisState &state) {
445 unsigned int minSize = std::min(bbArgs.size(), yieldedValues.size());
447 for (unsigned int i = 0; i < minSize; ++i) {
448 if (!isa<TensorLikeType>(bbArgs[i].getType()) ||
449 !isa<TensorLikeType>(yieldedValues[i].getType()))
450 continue;
451 if (state.areEquivalentBufferizedValues(bbArgs[i], yieldedValues[i]))
452 result.insert(i);
453 }
454 return result;
455}
456
457/// Helper function for loop bufferization. Return the bufferized values of the
458/// given OpOperands. If an operand is not a tensor, return the original value.
459static FailureOr<SmallVector<Value>>
460getBuffers(RewriterBase &rewriter, const MutableOperandRange &operands,
461 const BufferizationOptions &options, BufferizationState &state) {
462 SmallVector<Value> result;
463 for (OpOperand &opOperand : operands) {
464 if (isa<TensorLikeType>(opOperand.get().getType())) {
465 FailureOr<Value> resultBuffer =
466 getBuffer(rewriter, opOperand.get(), options, state);
467 if (failed(resultBuffer))
468 return failure();
469 result.push_back(*resultBuffer);
470 } else {
471 result.push_back(opOperand.get());
472 }
473 }
474 return result;
475}
476
477/// Helper function for loop bufferization. Given a list of bbArgs of the new
478/// (bufferized) loop op, wrap the bufferized tensor args (now memrefs) into
479/// ToTensorOps, so that the block body can be moved over to the new op.
480static SmallVector<Value>
481getBbArgReplacements(RewriterBase &rewriter, Block::BlockArgListType bbArgs,
482 Block::BlockArgListType oldBbArgs,
483 const DenseSet<int64_t> &tensorIndices) {
484 SmallVector<Value> result;
485 for (const auto &it : llvm::enumerate(bbArgs)) {
486 size_t idx = it.index();
487 Value val = it.value();
488 if (tensorIndices.contains(idx)) {
489 result.push_back(
490 bufferization::ToTensorOp::create(rewriter, val.getLoc(),
491 oldBbArgs[idx].getType(), val)
492 .getResult());
493 } else {
494 result.push_back(val);
495 }
496 }
497 return result;
498}
499
500/// Compute the bufferized type of a loop iter_arg. This type must be equal to
501/// the bufferized type of the corresponding init_arg and the bufferized type
502/// of the corresponding yielded value.
503///
504/// This function uses bufferization::getBufferType to compute the bufferized
505/// type of the init_arg and of the yielded value. (The computation of the
506/// bufferized yielded value type usually requires computing the bufferized type
507/// of the iter_arg again; the implementation of getBufferType traces back the
508/// use-def chain of the given value and computes a buffer type along the way.)
509/// If both buffer types are equal, no casts are needed the computed buffer type
510/// can be used directly. Otherwise, the buffer types can only differ in their
511/// layout map and a cast must be inserted.
512static FailureOr<BufferLikeType> computeLoopRegionIterArgBufferType(
513 Operation *loopOp, BlockArgument iterArg, Value initArg, Value yieldedValue,
514 const BufferizationOptions &options, const BufferizationState &state,
515 SmallVector<Value> &invocationStack) {
516 // Determine the buffer type of the init_arg.
517 auto initArgBufferType =
518 bufferization::getBufferType(initArg, options, state, invocationStack);
519 if (failed(initArgBufferType))
520 return failure();
521
522 if (llvm::count(invocationStack, iterArg) >= 2) {
523 // If the iter_arg is already twice on the invocation stack, just take the
524 // type of the init_arg. This is to avoid infinite loops when calculating
525 // the buffer type. This will most likely result in computing a memref type
526 // with a fully dynamic layout map.
527
528 // Note: For more precise layout map computation, a fixpoint iteration could
529 // be done (i.e., re-computing the yielded buffer type until the bufferized
530 // iter_arg type no longer changes). This current implementation immediately
531 // switches to a fully dynamic layout map when a mismatch between bufferized
532 // init_arg type and bufferized yield value type is detected.
533 return *initArgBufferType;
534 }
535
536 // Compute the buffer type of the yielded value.
537 BufferLikeType yieldedValueBufferType;
538 if (auto bufferType = dyn_cast<BufferLikeType>(yieldedValue.getType())) {
539 // scf.yield was already bufferized.
540 yieldedValueBufferType = bufferType;
541 } else {
542 // Note: This typically triggers a recursive call for the buffer type of
543 // the iter_arg.
544 auto maybeBufferType = bufferization::getBufferType(yieldedValue, options,
545 state, invocationStack);
546 if (failed(maybeBufferType))
547 return failure();
548 yieldedValueBufferType = *maybeBufferType;
549 }
550
551 // If yielded type and init_arg type are the same, use that type directly.
552 if (*initArgBufferType == yieldedValueBufferType)
553 return yieldedValueBufferType;
554
555 // If there is a mismatch between the yielded buffer type and the init_arg
556 // buffer type, the buffer type must be reconciled.
557#ifndef NDEBUG
558 if (auto iterTensorType = dyn_cast<TensorLikeType>(iterArg.getType())) {
559 const auto emitOpError = [&]() { return loopOp->emitOpError(); };
560 assert(succeeded(iterTensorType.verifyCompatibleBufferType(
561 yieldedValueBufferType, emitOpError)) &&
562 "incompatible yielded type");
563 assert(succeeded(iterTensorType.verifyCompatibleBufferType(
564 *initArgBufferType, emitOpError)) &&
565 "incompatible init_arg type");
566 }
567#endif // NDEBUG
568
569 auto reconciled = options.reconcileBufferTypeMismatchFn(
570 *initArgBufferType, yieldedValueBufferType, options);
571 if (failed(reconciled)) {
572 return loopOp->emitError(
573 "init_arg and yielded value bufferize to incompatible buffer types");
574 }
575
576 return *reconciled;
577}
578
579/// Return `true` if the given loop may have 0 iterations.
580bool mayHaveZeroIterations(scf::ForOp forOp) {
581 std::optional<int64_t> lb = getConstantIntValue(forOp.getLowerBound());
582 std::optional<int64_t> ub = getConstantIntValue(forOp.getUpperBound());
583 if (!lb.has_value() || !ub.has_value())
584 return true;
585 return *ub <= *lb;
586}
587
588/// Bufferization of scf.for. Replace with a new scf.for that operates on
589/// memrefs.
590struct ForOpInterface
591 : public BufferizableOpInterface::ExternalModel<ForOpInterface,
592 scf::ForOp> {
593 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
594 const AnalysisState &state) const {
595 auto forOp = cast<scf::ForOp>(op);
596
597 // If the loop has zero iterations, the results of the op are their
598 // corresponding init_args, meaning that the init_args bufferize to a read.
599 if (mayHaveZeroIterations(forOp))
600 return true;
601
602 // scf::ForOp alone doesn't bufferize to a memory read, one of the uses of
603 // its matching bbArg may.
604 return state.isValueRead(forOp.getTiedLoopRegionIterArg(&opOperand));
605 }
606
607 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
608 const AnalysisState &state) const {
609 // Tensor iter_args of scf::ForOps are always considered as a write.
610 return true;
611 }
612
613 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
614 const AnalysisState &state) const {
615 auto forOp = cast<scf::ForOp>(op);
616 OpResult opResult = forOp.getTiedLoopResult(&opOperand);
617 BufferRelation relation = bufferRelation(op, opResult, state);
618 return {{opResult, relation,
619 /*isDefinite=*/relation == BufferRelation::Equivalent}};
620 }
621
622 BufferRelation bufferRelation(Operation *op, OpResult opResult,
623 const AnalysisState &state) const {
624 // ForOp results are equivalent to their corresponding init_args if the
625 // corresponding iter_args and yield values are equivalent.
626 auto forOp = cast<scf::ForOp>(op);
627 BlockArgument bbArg = forOp.getTiedLoopRegionIterArg(opResult);
628 bool equivalentYield = state.areEquivalentBufferizedValues(
629 bbArg, forOp.getTiedLoopYieldedValue(bbArg)->get());
630 return equivalentYield ? BufferRelation::Equivalent
631 : BufferRelation::Unknown;
632 }
633
634 bool isWritable(Operation *op, Value value,
635 const AnalysisState &state) const {
636 // Interestingly, scf::ForOp's bbArg can **always** be viewed
637 // inplace from the perspective of ops nested under:
638 // 1. Either the matching iter operand is not bufferized inplace and an
639 // alloc + optional copy makes the bbArg itself inplaceable.
640 // 2. Or the matching iter operand is bufferized inplace and bbArg just
641 // bufferizes to that too.
642 return true;
643 }
644
645 LogicalResult
646 resolveConflicts(Operation *op, RewriterBase &rewriter,
647 const AnalysisState &analysisState,
648 const BufferizationState &bufferizationState) const {
649 auto bufferizableOp = cast<BufferizableOpInterface>(op);
650 if (failed(bufferizableOp.resolveTensorOpOperandConflicts(
651 rewriter, analysisState, bufferizationState)))
652 return failure();
653
654 if (analysisState.getOptions().copyBeforeWrite)
655 return success();
656
657 // According to the `getAliasing...` implementations, a bufferized OpResult
658 // may alias only with the corresponding bufferized init_arg (or with a
659 // newly allocated buffer) and not with other buffers defined outside of the
660 // loop. I.e., the i-th OpResult may alias with the i-th init_arg;
661 // but not with any other OpOperand.
662 auto forOp = cast<scf::ForOp>(op);
663 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
664 OpBuilder::InsertionGuard g(rewriter);
665 rewriter.setInsertionPoint(yieldOp);
666
667 // Indices of all iter_args that have tensor type. These are the ones that
668 // are bufferized.
669 DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs());
670 // For every yielded value, does it alias with something defined outside of
671 // the loop?
672 SmallVector<Value> yieldValues;
673 for (const auto it : llvm::enumerate(yieldOp.getResults())) {
674 // Note: `state` is guaranteed to be a `OneShotAnalysisState`, but this
675 // type cannot be used in the signature of `resolveConflicts` because the
676 // op interface is in the "IR" build unit and the `OneShotAnalysisState`
677 // is defined in the "Transforms" build unit.
678 if (!indices.contains(it.index()) ||
679 doesNotAliasExternalValue(
680 it.value(), &forOp.getRegion(),
681 /*exceptions=*/forOp.getRegionIterArg(it.index()),
682 static_cast<const OneShotAnalysisState &>(analysisState))) {
683 yieldValues.push_back(it.value());
684 continue;
685 }
686 FailureOr<Value> alloc = allocateTensorForShapedValue(
687 rewriter, yieldOp.getLoc(), it.value(), analysisState.getOptions(),
688 bufferizationState);
689 if (failed(alloc))
690 return failure();
691 yieldValues.push_back(*alloc);
692 }
693
694 rewriter.modifyOpInPlace(
695 yieldOp, [&]() { yieldOp.getResultsMutable().assign(yieldValues); });
696 return success();
697 }
698
699 FailureOr<BufferLikeType>
700 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
701 const BufferizationState &state,
702 SmallVector<Value> &invocationStack) const {
703 auto forOp = cast<scf::ForOp>(op);
704 assert(getOwnerOfValue(value) == op && "invalid value");
705 assert(isa<TensorLikeType>(value.getType()) && "expected tensor type");
706
707 if (auto opResult = dyn_cast<OpResult>(value)) {
708 // The type of an OpResult must match the corresponding iter_arg type.
709 BlockArgument bbArg = forOp.getTiedLoopRegionIterArg(opResult);
710 return bufferization::getBufferType(bbArg, options, state,
711 invocationStack);
712 }
713
714 // Compute result/argument number.
715 BlockArgument bbArg = cast<BlockArgument>(value);
716 unsigned resultNum = forOp.getTiedLoopResult(bbArg).getResultNumber();
717
718 // Compute the bufferized type.
719 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
720 Value yieldedValue = yieldOp.getOperand(resultNum);
721 BlockArgument iterArg = forOp.getRegionIterArgs()[resultNum];
722 Value initArg = forOp.getInitArgs()[resultNum];
723 return computeLoopRegionIterArgBufferType(
724 op, iterArg, initArg, yieldedValue, options, state, invocationStack);
725 }
726
727 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
728 const BufferizationOptions &options,
729 BufferizationState &state) const {
730 auto forOp = cast<scf::ForOp>(op);
731 Block *oldLoopBody = forOp.getBody();
732
733 // Indices of all iter_args that have tensor type. These are the ones that
734 // are bufferized.
735 DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs());
736
737 // The new memref init_args of the loop.
738 FailureOr<SmallVector<Value>> maybeInitArgs =
739 getBuffers(rewriter, forOp.getInitArgsMutable(), options, state);
740 if (failed(maybeInitArgs))
741 return failure();
742 SmallVector<Value> initArgs = *maybeInitArgs;
743
744 // Cast init_args if necessary.
745 SmallVector<Value> castedInitArgs;
746 for (const auto &it : llvm::enumerate(initArgs)) {
747 Value initArg = it.value();
748 Value result = forOp->getResult(it.index());
749 // If the type is not a tensor, bufferization doesn't need to touch it.
750 if (!isa<TensorLikeType>(result.getType())) {
751 castedInitArgs.push_back(initArg);
752 continue;
753 }
754 auto targetType = bufferization::getBufferType(result, options, state);
755 if (failed(targetType))
756 return failure();
757 castedInitArgs.push_back(
758 castBuffer(rewriter, initArg, *targetType, options));
759 }
760
761 // Construct a new scf.for op with memref instead of tensor values.
762 auto newForOp = scf::ForOp::create(
763 rewriter, forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(),
764 forOp.getStep(), castedInitArgs, /*bodyBuilder=*/nullptr,
765 forOp.getUnsignedCmp());
766 newForOp->setDiscardableAttrs(
767 forOp->getDiscardableAttrDictionary().getValue());
768 Block *loopBody = newForOp.getBody();
769
770 // Set up new iter_args. The loop body uses tensors, so wrap the (memref)
771 // iter_args of the new loop in ToTensorOps.
772 rewriter.setInsertionPointToStart(loopBody);
773 SmallVector<Value> iterArgs =
774 getBbArgReplacements(rewriter, newForOp.getRegionIterArgs(),
775 forOp.getRegionIterArgs(), indices);
776 iterArgs.insert(iterArgs.begin(), newForOp.getInductionVar());
777
778 // Move loop body to new loop.
779 rewriter.mergeBlocks(oldLoopBody, loopBody, iterArgs);
780
781 // Replace loop results.
782 replaceOpWithBufferizedValues(rewriter, op, newForOp->getResults());
783
784 return success();
785 }
786
787 /// Assert that yielded values of an scf.for op are equivalent to their
788 /// corresponding bbArgs. In that case, the buffer relations of the
789 /// corresponding OpResults are "Equivalent".
790 ///
791 /// If this is not the case, an allocs+copies are inserted and yielded from
792 /// the loop. This could be a performance problem, so it must be explicitly
793 /// activated with `alloc-return-allocs`.
794 LogicalResult verifyAnalysis(Operation *op,
795 const AnalysisState &state) const {
796 const auto &options =
797 static_cast<const OneShotBufferizationOptions &>(state.getOptions());
798 if (options.allowReturnAllocsFromLoops)
799 return success();
800
801 auto forOp = cast<scf::ForOp>(op);
802 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
803 for (OpResult opResult : op->getOpResults()) {
804 if (!isa<TensorLikeType>(opResult.getType()))
805 continue;
806 // Note: This is overly strict. We should check for aliasing bufferized
807 // values. But we don't have a "must-alias" analysis yet.
808 if (bufferRelation(op, opResult, state) != BufferRelation::Equivalent)
809 return yieldOp->emitError()
810 << "Yield operand #" << opResult.getResultNumber()
811 << " is not equivalent to the corresponding iter bbArg";
812 }
813
814 return success();
815 }
816};
817
818/// Bufferization of scf.while. Replace with a new scf.while that operates on
819/// memrefs.
820struct WhileOpInterface
821 : public BufferizableOpInterface::ExternalModel<WhileOpInterface,
822 scf::WhileOp> {
823 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
824 const AnalysisState &state) const {
825 // Tensor iter_args of scf::WhileOps are always considered as a read.
826 return true;
827 }
828
829 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
830 const AnalysisState &state) const {
831 // Tensor iter_args of scf::WhileOps are always considered as a write.
832 return true;
833 }
834
835 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
836 const AnalysisState &state) const {
837 auto whileOp = cast<scf::WhileOp>(op);
838 unsigned int idx = opOperand.getOperandNumber();
839
840 // The OpResults and OpOperands may not match. They may not even have the
841 // same type. The number of OpResults and OpOperands can also differ.
842 if (idx >= op->getNumResults() ||
843 opOperand.get().getType() != op->getResult(idx).getType())
844 return {};
845
846 // The only aliasing OpResult may be the one at the same index.
847 OpResult opResult = whileOp->getResult(idx);
848 BufferRelation relation = bufferRelation(op, opResult, state);
849 return {{opResult, relation,
850 /*isDefinite=*/relation == BufferRelation::Equivalent}};
851 }
852
853 BufferRelation bufferRelation(Operation *op, OpResult opResult,
854 const AnalysisState &state) const {
855 // WhileOp results are equivalent to their corresponding init_args if the
856 // corresponding iter_args and yield values are equivalent (for both the
857 // "before" and the "after" block).
858 unsigned int resultNumber = opResult.getResultNumber();
859 auto whileOp = cast<scf::WhileOp>(op);
860
861 // The "before" region bbArgs and the OpResults may not match.
862 if (resultNumber >= whileOp.getBeforeArguments().size())
863 return BufferRelation::Unknown;
864 if (opResult.getType() !=
865 whileOp.getBeforeArguments()[resultNumber].getType())
866 return BufferRelation::Unknown;
867
868 auto conditionOp = whileOp.getConditionOp();
869 BlockArgument conditionBbArg = whileOp.getBeforeArguments()[resultNumber];
870 Value conditionOperand = conditionOp.getArgs()[resultNumber];
871 bool equivCondition =
872 state.areEquivalentBufferizedValues(conditionBbArg, conditionOperand);
873
874 auto yieldOp = whileOp.getYieldOp();
875 BlockArgument bodyBbArg = whileOp.getAfterArguments()[resultNumber];
876 Value yieldOperand = yieldOp.getOperand(resultNumber);
877 bool equivYield =
878 state.areEquivalentBufferizedValues(bodyBbArg, yieldOperand);
879
880 return equivCondition && equivYield ? BufferRelation::Equivalent
881 : BufferRelation::Unknown;
882 }
883
884 bool isWritable(Operation *op, Value value,
885 const AnalysisState &state) const {
886 // Interestingly, scf::WhileOp's bbArg can **always** be viewed
887 // inplace from the perspective of ops nested under:
888 // 1. Either the matching iter operand is not bufferized inplace and an
889 // alloc + optional copy makes the bbArg itself inplaceable.
890 // 2. Or the matching iter operand is bufferized inplace and bbArg just
891 // bufferizes to that too.
892 return true;
893 }
894
895 LogicalResult
896 resolveConflicts(Operation *op, RewriterBase &rewriter,
897 const AnalysisState &analysisState,
898 const BufferizationState &bufferizationState) const {
899 auto bufferizableOp = cast<BufferizableOpInterface>(op);
900 if (failed(bufferizableOp.resolveTensorOpOperandConflicts(
901 rewriter, analysisState, bufferizationState)))
902 return failure();
903
904 if (analysisState.getOptions().copyBeforeWrite)
905 return success();
906
907 // According to the `getAliasing...` implementations, a bufferized OpResult
908 // may alias only with the corresponding bufferized init_arg and with no
909 // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg;
910 // but not with any other OpOperand. If a corresponding OpResult/init_arg
911 // pair bufferizes to equivalent buffers, this aliasing requirement is
912 // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy.
913 // (New buffer copies do not alias with any buffer.)
914 OpBuilder::InsertionGuard g(rewriter);
915 auto whileOp = cast<scf::WhileOp>(op);
916 auto conditionOp = whileOp.getConditionOp();
917
918 // For every yielded value, is the value equivalent to its corresponding
919 // bbArg?
920 DenseSet<int64_t> equivalentYieldsBefore = getEquivalentBuffers(
921 whileOp.getBeforeArguments(), conditionOp.getArgs(), analysisState);
922 DenseSet<int64_t> equivalentYieldsAfter =
923 getEquivalentBuffers(whileOp.getAfterArguments(),
924 whileOp.getYieldOp().getResults(), analysisState);
925
926 // Update "before" region.
927 rewriter.setInsertionPoint(conditionOp);
928 SmallVector<Value> beforeYieldValues;
929 for (int64_t idx = 0;
930 idx < static_cast<int64_t>(conditionOp.getArgs().size()); ++idx) {
931 Value value = conditionOp.getArgs()[idx];
932 if (!isa<TensorLikeType>(value.getType()) ||
933 (equivalentYieldsAfter.contains(idx) &&
934 equivalentYieldsBefore.contains(idx))) {
935 beforeYieldValues.push_back(value);
936 continue;
937 }
938 FailureOr<Value> alloc = allocateTensorForShapedValue(
939 rewriter, conditionOp.getLoc(), value, analysisState.getOptions(),
940 bufferizationState);
941 if (failed(alloc))
942 return failure();
943 beforeYieldValues.push_back(*alloc);
944 }
945 rewriter.modifyOpInPlace(conditionOp, [&]() {
946 conditionOp.getArgsMutable().assign(beforeYieldValues);
947 });
948
949 return success();
950 }
951
952 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
953 const BufferizationOptions &options,
954 BufferizationState &state) const {
955 auto whileOp = cast<scf::WhileOp>(op);
956
957 // Indices of all bbArgs that have tensor type. These are the ones that
958 // are bufferized. The "before" and "after" regions may have different args.
959 DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits());
960 DenseSet<int64_t> indicesAfter =
961 getTensorIndices(whileOp.getAfterArguments());
962
963 // The new memref init_args of the loop.
964 FailureOr<SmallVector<Value>> maybeInitArgs =
965 getBuffers(rewriter, whileOp.getInitsMutable(), options, state);
966 if (failed(maybeInitArgs))
967 return failure();
968 SmallVector<Value> initArgs = *maybeInitArgs;
969
970 // Cast init_args if necessary.
971 SmallVector<Value> castedInitArgs;
972 for (const auto &it : llvm::enumerate(initArgs)) {
973 Value initArg = it.value();
974 Value beforeArg = whileOp.getBeforeArguments()[it.index()];
975 // If the type is not a tensor, bufferization doesn't need to touch it.
976 if (!isa<TensorLikeType>(beforeArg.getType())) {
977 castedInitArgs.push_back(initArg);
978 continue;
979 }
980 auto targetType = bufferization::getBufferType(beforeArg, options, state);
981 if (failed(targetType))
982 return failure();
983 castedInitArgs.push_back(
984 castBuffer(rewriter, initArg, *targetType, options));
985 }
986
987 // The result types of a WhileOp are the same as the "after" bbArg types.
988 SmallVector<Type> argsTypesAfter = llvm::map_to_vector(
989 whileOp.getAfterArguments(), [&](BlockArgument bbArg) {
990 if (!isa<TensorLikeType>(bbArg.getType()))
991 return bbArg.getType();
992 // TODO: error handling
993 return llvm::cast<Type>(
994 *bufferization::getBufferType(bbArg, options, state));
995 });
996
997 // Construct a new scf.while op with memref instead of tensor values.
998 ValueRange argsRangeBefore(castedInitArgs);
999 TypeRange argsTypesBefore(argsRangeBefore);
1000 auto newWhileOp = scf::WhileOp::create(rewriter, whileOp.getLoc(),
1001 argsTypesAfter, castedInitArgs);
1002
1003 // Add before/after regions to the new op.
1004 SmallVector<Location> bbArgLocsBefore(castedInitArgs.size(),
1005 whileOp.getLoc());
1006 SmallVector<Location> bbArgLocsAfter(argsTypesAfter.size(),
1007 whileOp.getLoc());
1008 Block *newBeforeBody = &newWhileOp.getBefore().emplaceBlock();
1009 newWhileOp.getBefore().addArguments(argsTypesBefore, bbArgLocsBefore);
1010 Block *newAfterBody = &newWhileOp.getAfter().emplaceBlock();
1011 newWhileOp.getAfter().addArguments(argsTypesAfter, bbArgLocsAfter);
1012
1013 // Set up new iter_args and move the loop condition block to the new op.
1014 // The old block uses tensors, so wrap the (memref) bbArgs of the new block
1015 // in ToTensorOps.
1016 rewriter.setInsertionPointToStart(newBeforeBody);
1017 SmallVector<Value> newBeforeArgs =
1018 getBbArgReplacements(rewriter, newWhileOp.getBeforeArguments(),
1019 whileOp.getBeforeArguments(), indicesBefore);
1020 rewriter.mergeBlocks(whileOp.getBeforeBody(), newBeforeBody, newBeforeArgs);
1021
1022 // Set up new iter_args and move the loop body block to the new op.
1023 // The old block uses tensors, so wrap the (memref) bbArgs of the new block
1024 // in ToTensorOps.
1025 rewriter.setInsertionPointToStart(newAfterBody);
1026 SmallVector<Value> newAfterArgs =
1027 getBbArgReplacements(rewriter, newWhileOp.getAfterArguments(),
1028 whileOp.getAfterArguments(), indicesAfter);
1029 rewriter.mergeBlocks(whileOp.getAfterBody(), newAfterBody, newAfterArgs);
1030
1031 // Replace loop results.
1032 replaceOpWithBufferizedValues(rewriter, op, newWhileOp->getResults());
1033
1034 return success();
1035 }
1036
1037 FailureOr<BufferLikeType>
1038 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
1039 const BufferizationState &state,
1040 SmallVector<Value> &invocationStack) const {
1041 auto whileOp = cast<scf::WhileOp>(op);
1042 assert(getOwnerOfValue(value) == op && "invalid value");
1043 assert(isa<TensorLikeType>(value.getType()) && "expected tensor type");
1044
1045 // Case 1: Block argument of the "before" region.
1046 if (auto bbArg = dyn_cast<BlockArgument>(value)) {
1047 if (bbArg.getOwner()->getParent() == &whileOp.getBefore()) {
1048 Value initArg = whileOp.getInits()[bbArg.getArgNumber()];
1049 auto yieldOp = whileOp.getYieldOp();
1050 Value yieldedValue = yieldOp.getOperand(bbArg.getArgNumber());
1051 return computeLoopRegionIterArgBufferType(
1052 op, bbArg, initArg, yieldedValue, options, state, invocationStack);
1053 }
1054 }
1055
1056 // Case 2: OpResult of the loop or block argument of the "after" region.
1057 // The bufferized "after" bbArg type can be directly computed from the
1058 // bufferized "before" bbArg type.
1059 unsigned resultNum;
1060 if (auto opResult = dyn_cast<OpResult>(value)) {
1061 resultNum = opResult.getResultNumber();
1062 } else if (cast<BlockArgument>(value).getOwner()->getParent() ==
1063 &whileOp.getAfter()) {
1064 resultNum = cast<BlockArgument>(value).getArgNumber();
1065 } else {
1066 llvm_unreachable("invalid value");
1067 }
1068 Value conditionYieldedVal = whileOp.getConditionOp().getArgs()[resultNum];
1069 if (!isa<TensorLikeType>(conditionYieldedVal.getType())) {
1070 // scf.condition was already bufferized.
1071 return cast<BufferLikeType>(conditionYieldedVal.getType());
1072 }
1073 return bufferization::getBufferType(conditionYieldedVal, options, state,
1074 invocationStack);
1075 }
1076
1077 /// Assert that yielded values of an scf.while op are equivalent to their
1078 /// corresponding bbArgs. In that case, the buffer relations of the
1079 /// corresponding OpResults are "Equivalent".
1080 ///
1081 /// If this is not the case, allocs+copies are inserted and yielded from
1082 /// the loop. This could be a performance problem, so it must be explicitly
1083 /// activated with `allow-return-allocs`.
1084 ///
1085 /// Not: In contrast to scf::ForOp, scf::WhileOp has two regions and the
1086 /// equivalence condition must be checked for both.
1087 LogicalResult verifyAnalysis(Operation *op,
1088 const AnalysisState &state) const {
1089 auto whileOp = cast<scf::WhileOp>(op);
1090 const auto &options =
1091 static_cast<const OneShotBufferizationOptions &>(state.getOptions());
1092 if (options.allowReturnAllocsFromLoops)
1093 return success();
1094
1095 auto conditionOp = whileOp.getConditionOp();
1096 for (const auto &it : llvm::enumerate(conditionOp.getArgs())) {
1097 Block *block = conditionOp->getBlock();
1098 if (!isa<TensorLikeType>(it.value().getType()))
1099 continue;
1100 if (it.index() >= block->getNumArguments() ||
1101 !state.areEquivalentBufferizedValues(it.value(),
1102 block->getArgument(it.index())))
1103 return conditionOp->emitError()
1104 << "Condition arg #" << it.index()
1105 << " is not equivalent to the corresponding iter bbArg";
1106 }
1107
1108 auto yieldOp = whileOp.getYieldOp();
1109 for (const auto &it : llvm::enumerate(yieldOp.getResults())) {
1110 Block *block = yieldOp->getBlock();
1111 if (!isa<TensorLikeType>(it.value().getType()))
1112 continue;
1113 if (it.index() >= block->getNumArguments() ||
1114 !state.areEquivalentBufferizedValues(it.value(),
1115 block->getArgument(it.index())))
1116 return yieldOp->emitError()
1117 << "Yield operand #" << it.index()
1118 << " is not equivalent to the corresponding iter bbArg";
1119 }
1120
1121 return success();
1122 }
1123};
1124
1125/// Bufferization of scf.yield. Bufferized as part of their enclosing ops, so
1126/// this is for analysis only.
1127struct YieldOpInterface
1128 : public BufferizableOpInterface::ExternalModel<YieldOpInterface,
1129 scf::YieldOp> {
1130 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
1131 const AnalysisState &state) const {
1132 return true;
1133 }
1134
1135 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
1136 const AnalysisState &state) const {
1137 return false;
1138 }
1139
1140 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
1141 const AnalysisState &state) const {
1142 if (auto ifOp = dyn_cast<scf::IfOp>(op->getParentOp())) {
1143 return {{op->getParentOp()->getResult(opOperand.getOperandNumber()),
1144 BufferRelation::Equivalent, /*isDefinite=*/false}};
1145 }
1146 if (isa<scf::ExecuteRegionOp>(op->getParentOp()))
1147 return {{op->getParentOp()->getResult(opOperand.getOperandNumber()),
1148 BufferRelation::Equivalent}};
1149 return {};
1150 }
1151
1152 bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand,
1153 const AnalysisState &state) const {
1154 // Yield operands always bufferize inplace. Otherwise, an alloc + copy
1155 // may be generated inside the block. We should not return/yield allocations
1156 // when possible.
1157 return true;
1158 }
1159
1160 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
1161 const BufferizationOptions &options,
1162 BufferizationState &state) const {
1163 auto yieldOp = cast<scf::YieldOp>(op);
1164 if (!isa<scf::ExecuteRegionOp, scf::IfOp, scf::IndexSwitchOp, scf::ForOp,
1165 scf::WhileOp>(yieldOp->getParentOp()))
1166 return yieldOp->emitError("unsupported scf::YieldOp parent");
1167
1168 SmallVector<Value> newResults;
1169 for (const auto &it : llvm::enumerate(yieldOp.getResults())) {
1170 Value value = it.value();
1171 if (isa<TensorLikeType>(value.getType())) {
1172 FailureOr<Value> maybeBuffer =
1173 getBuffer(rewriter, value, options, state);
1174 if (failed(maybeBuffer))
1175 return failure();
1176 Value buffer = *maybeBuffer;
1177 // We may have to cast the value before yielding it.
1178 if (isa<scf::ForOp, scf::IfOp, scf::IndexSwitchOp>(
1179 yieldOp->getParentOp())) {
1180 FailureOr<BufferLikeType> resultType = bufferization::getBufferType(
1181 yieldOp->getParentOp()->getResult(it.index()), options, state);
1182 if (failed(resultType))
1183 return failure();
1184 buffer = castBuffer(rewriter, buffer, *resultType, options);
1185 } else if (auto whileOp =
1186 dyn_cast<scf::WhileOp>(yieldOp->getParentOp())) {
1187 FailureOr<BufferLikeType> resultType = bufferization::getBufferType(
1188 whileOp.getBeforeArguments()[it.index()], options, state);
1189 if (failed(resultType))
1190 return failure();
1191 buffer = castBuffer(rewriter, buffer, *resultType, options);
1192 }
1193 newResults.push_back(buffer);
1194 } else {
1195 newResults.push_back(value);
1196 }
1197 }
1198
1199 replaceOpWithNewBufferizedOp<scf::YieldOp>(rewriter, op, newResults);
1200 return success();
1201 }
1202};
1203
1204/// Bufferization of ForallOp. This also bufferizes the terminator of the
1205/// region. There are op interfaces for the terminators (InParallelOp
1206/// and ParallelInsertSliceOp), but these are only used during analysis. Not
1207/// for bufferization.
1208struct ForallOpInterface
1209 : public BufferizableOpInterface::ExternalModel<ForallOpInterface,
1210 ForallOp> {
1211 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
1212 const AnalysisState &state) const {
1213 // All tensor operands to `scf.forall` are `shared_outs` and all
1214 // shared outs are assumed to be read by the loop. This does not
1215 // account for the case where the entire value is over-written,
1216 // but being conservative here.
1217 return true;
1218 }
1219
1220 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
1221 const AnalysisState &state) const {
1222 // Outputs of scf::ForallOps are always considered as a write.
1223 return true;
1224 }
1225
1226 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
1227 const AnalysisState &state) const {
1228 auto forallOp = cast<ForallOp>(op);
1229 return {
1230 {{forallOp.getTiedOpResult(&opOperand), BufferRelation::Equivalent}}};
1231 }
1232
1233 bool isWritable(Operation *op, Value value,
1234 const AnalysisState &state) const {
1235 return true;
1236 }
1237
1238 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
1239 const BufferizationOptions &options,
1240 BufferizationState &state) const {
1241 OpBuilder::InsertionGuard guard(rewriter);
1242 auto forallOp = cast<ForallOp>(op);
1243 int64_t rank = forallOp.getRank();
1244
1245 // Get buffers for all output operands.
1246 SmallVector<Value> buffers;
1247 for (Value out : forallOp.getOutputs()) {
1248 FailureOr<Value> buffer = getBuffer(rewriter, out, options, state);
1249 if (failed(buffer))
1250 return failure();
1251 buffers.push_back(*buffer);
1252 }
1253
1254 // Use buffers instead of block arguments.
1255 rewriter.setInsertionPointToStart(forallOp.getBody());
1256 for (const auto &it : llvm::zip(
1257 forallOp.getBody()->getArguments().drop_front(rank), buffers)) {
1258 BlockArgument bbArg = std::get<0>(it);
1259 Value buffer = std::get<1>(it);
1260 Value bufferAsTensor = ToTensorOp::create(rewriter, forallOp.getLoc(),
1261 bbArg.getType(), buffer);
1262 bbArg.replaceAllUsesWith(bufferAsTensor);
1263 }
1264
1265 // Create new ForallOp without any results and drop the automatically
1266 // introduced terminator.
1267 rewriter.setInsertionPoint(forallOp);
1268 ForallOp newForallOp;
1269 newForallOp = ForallOp::create(
1270 rewriter, forallOp.getLoc(), forallOp.getMixedLowerBound(),
1271 forallOp.getMixedUpperBound(), forallOp.getMixedStep(),
1272 /*outputs=*/ValueRange(), forallOp.getMapping());
1273
1274 // Keep discardable attributes from the original op.
1275 newForallOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1276
1277 rewriter.eraseOp(newForallOp.getBody()->getTerminator());
1278
1279 // Move over block contents of the old op.
1280 SmallVector<Value> replacementBbArgs;
1281 replacementBbArgs.append(newForallOp.getBody()->getArguments().begin(),
1282 newForallOp.getBody()->getArguments().end());
1283 replacementBbArgs.append(forallOp.getOutputs().size(), Value());
1284 rewriter.mergeBlocks(forallOp.getBody(), newForallOp.getBody(),
1285 replacementBbArgs);
1286
1287 // Remove the old op and replace all of its uses.
1288 replaceOpWithBufferizedValues(rewriter, op, buffers);
1289
1290 return success();
1291 }
1292
1293 FailureOr<BufferLikeType>
1294 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
1295 const BufferizationState &state,
1296 SmallVector<Value> &invocationStack) const {
1297 auto forallOp = cast<ForallOp>(op);
1298
1299 if (auto bbArg = dyn_cast<BlockArgument>(value))
1300 // A tensor block argument has the same bufferized type as the
1301 // corresponding output operand.
1302 return bufferization::getBufferType(
1303 forallOp.getTiedOpOperand(bbArg)->get(), options, state,
1304 invocationStack);
1305
1306 // The bufferized result type is the same as the bufferized type of the
1307 // corresponding output operand.
1308 return bufferization::getBufferType(
1309 forallOp.getOutputs()[cast<OpResult>(value).getResultNumber()], options,
1310 state, invocationStack);
1311 }
1312
1313 bool isRepetitiveRegion(Operation *op, unsigned index) const {
1314 auto forallOp = cast<ForallOp>(op);
1315
1316 // This op is repetitive if it has 1 or more steps.
1317 // If the control variables are dynamic, it is also considered so.
1318 for (auto [lb, ub, step] :
1319 llvm::zip(forallOp.getMixedLowerBound(), forallOp.getMixedUpperBound(),
1320 forallOp.getMixedStep())) {
1321 std::optional<int64_t> lbConstant = getConstantIntValue(lb);
1322 if (!lbConstant)
1323 return true;
1324
1325 std::optional<int64_t> ubConstant = getConstantIntValue(ub);
1326 if (!ubConstant)
1327 return true;
1328
1329 std::optional<int64_t> stepConstant = getConstantIntValue(step);
1330 if (!stepConstant)
1331 return true;
1332
1333 if (*lbConstant + *stepConstant < *ubConstant)
1334 return true;
1335 }
1336 return false;
1337 }
1338
1339 bool isParallelRegion(Operation *op, unsigned index) const {
1340 return isRepetitiveRegion(op, index);
1341 }
1342};
1343
1344/// Nothing to do for InParallelOp.
1345struct InParallelOpInterface
1346 : public BufferizableOpInterface::ExternalModel<InParallelOpInterface,
1347 InParallelOp> {
1348 LogicalResult bufferize(Operation *op, RewriterBase &b,
1349 const BufferizationOptions &options,
1350 BufferizationState &state) const {
1351 llvm_unreachable("op does not have any tensor OpOperands / OpResults");
1352 return failure();
1353 }
1354};
1355
1356} // namespace
1357} // namespace scf
1358} // namespace mlir
1359
1361 DialectRegistry &registry) {
1362 registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) {
1363 ConditionOp::attachInterface<ConditionOpInterface>(*ctx);
1364 ExecuteRegionOp::attachInterface<ExecuteRegionOpInterface>(*ctx);
1365 ForOp::attachInterface<ForOpInterface>(*ctx);
1366 IfOp::attachInterface<IfOpInterface>(*ctx);
1367 IndexSwitchOp::attachInterface<IndexSwitchOpInterface>(*ctx);
1368 ForallOp::attachInterface<ForallOpInterface>(*ctx);
1369 InParallelOp::attachInterface<InParallelOpInterface>(*ctx);
1370 WhileOp::attachInterface<WhileOpInterface>(*ctx);
1371 YieldOp::attachInterface<YieldOpInterface>(*ctx);
1372 });
1373}
return success()
static bool isRepetitiveRegion(Region *region, const BufferizationOptions &options)
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static llvm::ManagedStatic< PassManagerOptions > options
static RankedTensorType getBufferType(const SparseTensorType &stt, bool needTmpCOO)
static Operation * getOwnerOfValue(Value value)
unsigned getArgNumber() const
Returns the number of this argument.
Definition Value.h:318
Block * getOwner() const
Returns the block that owns this argument.
Definition Value.h:315
MutableArrayRef< BlockArgument > BlockArgListType
Definition Block.h:109
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
IRValueT get() const
Return the current value being used by this operand.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
unsigned getResultNumber() const
Returns the number of this result.
Definition Value.h:466
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
DictionaryAttr getDiscardableAttrDictionary()
Return all of the discardable attributes on this operation as a DictionaryAttr.
Definition Operation.h:553
result_range getOpResults()
Definition Operation.h:445
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:249
bool isProperAncestor(Region *other)
Return true if this region is a proper ancestor of the other region.
Definition Region.cpp:50
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
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.
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
Type getType() const
Return the type of this value.
Definition Value.h:105
void replaceAllUsesWith(Value newValue)
Replace all uses of 'this' value with the new value, updating anything in the IR that uses 'this' to ...
Definition Value.h:149
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
void applyOnAliases(Value v, function_ref< void(Value)> fun) const
Apply fun to all aliases of v.
LogicalResult bufferizeBlockSignature(Block *block, RewriterBase &rewriter, const BufferizationOptions &options, BufferizationState &state)
Bufferize the signature of block and its callers (i.e., ops that have the given block as a successor)...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
void registerBufferizableOpInterfaceExternalModels(DialectRegistry &registry)
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122