MLIR 23.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->setAttrs(forOp->getAttrs());
767 Block *loopBody = newForOp.getBody();
768
769 // Set up new iter_args. The loop body uses tensors, so wrap the (memref)
770 // iter_args of the new loop in ToTensorOps.
771 rewriter.setInsertionPointToStart(loopBody);
772 SmallVector<Value> iterArgs =
773 getBbArgReplacements(rewriter, newForOp.getRegionIterArgs(),
774 forOp.getRegionIterArgs(), indices);
775 iterArgs.insert(iterArgs.begin(), newForOp.getInductionVar());
776
777 // Move loop body to new loop.
778 rewriter.mergeBlocks(oldLoopBody, loopBody, iterArgs);
779
780 // Replace loop results.
781 replaceOpWithBufferizedValues(rewriter, op, newForOp->getResults());
782
783 return success();
784 }
785
786 /// Assert that yielded values of an scf.for op are equivalent to their
787 /// corresponding bbArgs. In that case, the buffer relations of the
788 /// corresponding OpResults are "Equivalent".
789 ///
790 /// If this is not the case, an allocs+copies are inserted and yielded from
791 /// the loop. This could be a performance problem, so it must be explicitly
792 /// activated with `alloc-return-allocs`.
793 LogicalResult verifyAnalysis(Operation *op,
794 const AnalysisState &state) const {
795 const auto &options =
796 static_cast<const OneShotBufferizationOptions &>(state.getOptions());
797 if (options.allowReturnAllocsFromLoops)
798 return success();
799
800 auto forOp = cast<scf::ForOp>(op);
801 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
802 for (OpResult opResult : op->getOpResults()) {
803 if (!isa<TensorLikeType>(opResult.getType()))
804 continue;
805 // Note: This is overly strict. We should check for aliasing bufferized
806 // values. But we don't have a "must-alias" analysis yet.
807 if (bufferRelation(op, opResult, state) != BufferRelation::Equivalent)
808 return yieldOp->emitError()
809 << "Yield operand #" << opResult.getResultNumber()
810 << " is not equivalent to the corresponding iter bbArg";
811 }
812
813 return success();
814 }
815};
816
817/// Bufferization of scf.while. Replace with a new scf.while that operates on
818/// memrefs.
819struct WhileOpInterface
820 : public BufferizableOpInterface::ExternalModel<WhileOpInterface,
821 scf::WhileOp> {
822 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
823 const AnalysisState &state) const {
824 // Tensor iter_args of scf::WhileOps are always considered as a read.
825 return true;
826 }
827
828 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
829 const AnalysisState &state) const {
830 // Tensor iter_args of scf::WhileOps are always considered as a write.
831 return true;
832 }
833
834 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
835 const AnalysisState &state) const {
836 auto whileOp = cast<scf::WhileOp>(op);
837 unsigned int idx = opOperand.getOperandNumber();
838
839 // The OpResults and OpOperands may not match. They may not even have the
840 // same type. The number of OpResults and OpOperands can also differ.
841 if (idx >= op->getNumResults() ||
842 opOperand.get().getType() != op->getResult(idx).getType())
843 return {};
844
845 // The only aliasing OpResult may be the one at the same index.
846 OpResult opResult = whileOp->getResult(idx);
847 BufferRelation relation = bufferRelation(op, opResult, state);
848 return {{opResult, relation,
849 /*isDefinite=*/relation == BufferRelation::Equivalent}};
850 }
851
852 BufferRelation bufferRelation(Operation *op, OpResult opResult,
853 const AnalysisState &state) const {
854 // WhileOp results are equivalent to their corresponding init_args if the
855 // corresponding iter_args and yield values are equivalent (for both the
856 // "before" and the "after" block).
857 unsigned int resultNumber = opResult.getResultNumber();
858 auto whileOp = cast<scf::WhileOp>(op);
859
860 // The "before" region bbArgs and the OpResults may not match.
861 if (resultNumber >= whileOp.getBeforeArguments().size())
862 return BufferRelation::Unknown;
863 if (opResult.getType() !=
864 whileOp.getBeforeArguments()[resultNumber].getType())
865 return BufferRelation::Unknown;
866
867 auto conditionOp = whileOp.getConditionOp();
868 BlockArgument conditionBbArg = whileOp.getBeforeArguments()[resultNumber];
869 Value conditionOperand = conditionOp.getArgs()[resultNumber];
870 bool equivCondition =
871 state.areEquivalentBufferizedValues(conditionBbArg, conditionOperand);
872
873 auto yieldOp = whileOp.getYieldOp();
874 BlockArgument bodyBbArg = whileOp.getAfterArguments()[resultNumber];
875 Value yieldOperand = yieldOp.getOperand(resultNumber);
876 bool equivYield =
877 state.areEquivalentBufferizedValues(bodyBbArg, yieldOperand);
878
879 return equivCondition && equivYield ? BufferRelation::Equivalent
880 : BufferRelation::Unknown;
881 }
882
883 bool isWritable(Operation *op, Value value,
884 const AnalysisState &state) const {
885 // Interestingly, scf::WhileOp's bbArg can **always** be viewed
886 // inplace from the perspective of ops nested under:
887 // 1. Either the matching iter operand is not bufferized inplace and an
888 // alloc + optional copy makes the bbArg itself inplaceable.
889 // 2. Or the matching iter operand is bufferized inplace and bbArg just
890 // bufferizes to that too.
891 return true;
892 }
893
894 LogicalResult
895 resolveConflicts(Operation *op, RewriterBase &rewriter,
896 const AnalysisState &analysisState,
897 const BufferizationState &bufferizationState) const {
898 auto bufferizableOp = cast<BufferizableOpInterface>(op);
899 if (failed(bufferizableOp.resolveTensorOpOperandConflicts(
900 rewriter, analysisState, bufferizationState)))
901 return failure();
902
903 if (analysisState.getOptions().copyBeforeWrite)
904 return success();
905
906 // According to the `getAliasing...` implementations, a bufferized OpResult
907 // may alias only with the corresponding bufferized init_arg and with no
908 // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg;
909 // but not with any other OpOperand. If a corresponding OpResult/init_arg
910 // pair bufferizes to equivalent buffers, this aliasing requirement is
911 // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy.
912 // (New buffer copies do not alias with any buffer.)
913 OpBuilder::InsertionGuard g(rewriter);
914 auto whileOp = cast<scf::WhileOp>(op);
915 auto conditionOp = whileOp.getConditionOp();
916
917 // For every yielded value, is the value equivalent to its corresponding
918 // bbArg?
919 DenseSet<int64_t> equivalentYieldsBefore = getEquivalentBuffers(
920 whileOp.getBeforeArguments(), conditionOp.getArgs(), analysisState);
921 DenseSet<int64_t> equivalentYieldsAfter =
922 getEquivalentBuffers(whileOp.getAfterArguments(),
923 whileOp.getYieldOp().getResults(), analysisState);
924
925 // Update "before" region.
926 rewriter.setInsertionPoint(conditionOp);
927 SmallVector<Value> beforeYieldValues;
928 for (int64_t idx = 0;
929 idx < static_cast<int64_t>(conditionOp.getArgs().size()); ++idx) {
930 Value value = conditionOp.getArgs()[idx];
931 if (!isa<TensorLikeType>(value.getType()) ||
932 (equivalentYieldsAfter.contains(idx) &&
933 equivalentYieldsBefore.contains(idx))) {
934 beforeYieldValues.push_back(value);
935 continue;
936 }
937 FailureOr<Value> alloc = allocateTensorForShapedValue(
938 rewriter, conditionOp.getLoc(), value, analysisState.getOptions(),
939 bufferizationState);
940 if (failed(alloc))
941 return failure();
942 beforeYieldValues.push_back(*alloc);
943 }
944 rewriter.modifyOpInPlace(conditionOp, [&]() {
945 conditionOp.getArgsMutable().assign(beforeYieldValues);
946 });
947
948 return success();
949 }
950
951 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
952 const BufferizationOptions &options,
953 BufferizationState &state) const {
954 auto whileOp = cast<scf::WhileOp>(op);
955
956 // Indices of all bbArgs that have tensor type. These are the ones that
957 // are bufferized. The "before" and "after" regions may have different args.
958 DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits());
959 DenseSet<int64_t> indicesAfter =
960 getTensorIndices(whileOp.getAfterArguments());
961
962 // The new memref init_args of the loop.
963 FailureOr<SmallVector<Value>> maybeInitArgs =
964 getBuffers(rewriter, whileOp.getInitsMutable(), options, state);
965 if (failed(maybeInitArgs))
966 return failure();
967 SmallVector<Value> initArgs = *maybeInitArgs;
968
969 // Cast init_args if necessary.
970 SmallVector<Value> castedInitArgs;
971 for (const auto &it : llvm::enumerate(initArgs)) {
972 Value initArg = it.value();
973 Value beforeArg = whileOp.getBeforeArguments()[it.index()];
974 // If the type is not a tensor, bufferization doesn't need to touch it.
975 if (!isa<TensorLikeType>(beforeArg.getType())) {
976 castedInitArgs.push_back(initArg);
977 continue;
978 }
979 auto targetType = bufferization::getBufferType(beforeArg, options, state);
980 if (failed(targetType))
981 return failure();
982 castedInitArgs.push_back(
983 castBuffer(rewriter, initArg, *targetType, options));
984 }
985
986 // The result types of a WhileOp are the same as the "after" bbArg types.
987 SmallVector<Type> argsTypesAfter = llvm::map_to_vector(
988 whileOp.getAfterArguments(), [&](BlockArgument bbArg) {
989 if (!isa<TensorLikeType>(bbArg.getType()))
990 return bbArg.getType();
991 // TODO: error handling
992 return llvm::cast<Type>(
993 *bufferization::getBufferType(bbArg, options, state));
994 });
995
996 // Construct a new scf.while op with memref instead of tensor values.
997 ValueRange argsRangeBefore(castedInitArgs);
998 TypeRange argsTypesBefore(argsRangeBefore);
999 auto newWhileOp = scf::WhileOp::create(rewriter, whileOp.getLoc(),
1000 argsTypesAfter, castedInitArgs);
1001
1002 // Add before/after regions to the new op.
1003 SmallVector<Location> bbArgLocsBefore(castedInitArgs.size(),
1004 whileOp.getLoc());
1005 SmallVector<Location> bbArgLocsAfter(argsTypesAfter.size(),
1006 whileOp.getLoc());
1007 Block *newBeforeBody = &newWhileOp.getBefore().emplaceBlock();
1008 newWhileOp.getBefore().addArguments(argsTypesBefore, bbArgLocsBefore);
1009 Block *newAfterBody = &newWhileOp.getAfter().emplaceBlock();
1010 newWhileOp.getAfter().addArguments(argsTypesAfter, bbArgLocsAfter);
1011
1012 // Set up new iter_args and move the loop condition block to the new op.
1013 // The old block uses tensors, so wrap the (memref) bbArgs of the new block
1014 // in ToTensorOps.
1015 rewriter.setInsertionPointToStart(newBeforeBody);
1016 SmallVector<Value> newBeforeArgs =
1017 getBbArgReplacements(rewriter, newWhileOp.getBeforeArguments(),
1018 whileOp.getBeforeArguments(), indicesBefore);
1019 rewriter.mergeBlocks(whileOp.getBeforeBody(), newBeforeBody, newBeforeArgs);
1020
1021 // Set up new iter_args and move the loop body block to the new op.
1022 // The old block uses tensors, so wrap the (memref) bbArgs of the new block
1023 // in ToTensorOps.
1024 rewriter.setInsertionPointToStart(newAfterBody);
1025 SmallVector<Value> newAfterArgs =
1026 getBbArgReplacements(rewriter, newWhileOp.getAfterArguments(),
1027 whileOp.getAfterArguments(), indicesAfter);
1028 rewriter.mergeBlocks(whileOp.getAfterBody(), newAfterBody, newAfterArgs);
1029
1030 // Replace loop results.
1031 replaceOpWithBufferizedValues(rewriter, op, newWhileOp->getResults());
1032
1033 return success();
1034 }
1035
1036 FailureOr<BufferLikeType>
1037 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
1038 const BufferizationState &state,
1039 SmallVector<Value> &invocationStack) const {
1040 auto whileOp = cast<scf::WhileOp>(op);
1041 assert(getOwnerOfValue(value) == op && "invalid value");
1042 assert(isa<TensorLikeType>(value.getType()) && "expected tensor type");
1043
1044 // Case 1: Block argument of the "before" region.
1045 if (auto bbArg = dyn_cast<BlockArgument>(value)) {
1046 if (bbArg.getOwner()->getParent() == &whileOp.getBefore()) {
1047 Value initArg = whileOp.getInits()[bbArg.getArgNumber()];
1048 auto yieldOp = whileOp.getYieldOp();
1049 Value yieldedValue = yieldOp.getOperand(bbArg.getArgNumber());
1050 return computeLoopRegionIterArgBufferType(
1051 op, bbArg, initArg, yieldedValue, options, state, invocationStack);
1052 }
1053 }
1054
1055 // Case 2: OpResult of the loop or block argument of the "after" region.
1056 // The bufferized "after" bbArg type can be directly computed from the
1057 // bufferized "before" bbArg type.
1058 unsigned resultNum;
1059 if (auto opResult = dyn_cast<OpResult>(value)) {
1060 resultNum = opResult.getResultNumber();
1061 } else if (cast<BlockArgument>(value).getOwner()->getParent() ==
1062 &whileOp.getAfter()) {
1063 resultNum = cast<BlockArgument>(value).getArgNumber();
1064 } else {
1065 llvm_unreachable("invalid value");
1066 }
1067 Value conditionYieldedVal = whileOp.getConditionOp().getArgs()[resultNum];
1068 if (!isa<TensorLikeType>(conditionYieldedVal.getType())) {
1069 // scf.condition was already bufferized.
1070 return cast<BufferLikeType>(conditionYieldedVal.getType());
1071 }
1072 return bufferization::getBufferType(conditionYieldedVal, options, state,
1073 invocationStack);
1074 }
1075
1076 /// Assert that yielded values of an scf.while op are equivalent to their
1077 /// corresponding bbArgs. In that case, the buffer relations of the
1078 /// corresponding OpResults are "Equivalent".
1079 ///
1080 /// If this is not the case, allocs+copies are inserted and yielded from
1081 /// the loop. This could be a performance problem, so it must be explicitly
1082 /// activated with `allow-return-allocs`.
1083 ///
1084 /// Not: In contrast to scf::ForOp, scf::WhileOp has two regions and the
1085 /// equivalence condition must be checked for both.
1086 LogicalResult verifyAnalysis(Operation *op,
1087 const AnalysisState &state) const {
1088 auto whileOp = cast<scf::WhileOp>(op);
1089 const auto &options =
1090 static_cast<const OneShotBufferizationOptions &>(state.getOptions());
1091 if (options.allowReturnAllocsFromLoops)
1092 return success();
1093
1094 auto conditionOp = whileOp.getConditionOp();
1095 for (const auto &it : llvm::enumerate(conditionOp.getArgs())) {
1096 Block *block = conditionOp->getBlock();
1097 if (!isa<TensorLikeType>(it.value().getType()))
1098 continue;
1099 if (it.index() >= block->getNumArguments() ||
1100 !state.areEquivalentBufferizedValues(it.value(),
1101 block->getArgument(it.index())))
1102 return conditionOp->emitError()
1103 << "Condition arg #" << it.index()
1104 << " is not equivalent to the corresponding iter bbArg";
1105 }
1106
1107 auto yieldOp = whileOp.getYieldOp();
1108 for (const auto &it : llvm::enumerate(yieldOp.getResults())) {
1109 Block *block = yieldOp->getBlock();
1110 if (!isa<TensorLikeType>(it.value().getType()))
1111 continue;
1112 if (it.index() >= block->getNumArguments() ||
1113 !state.areEquivalentBufferizedValues(it.value(),
1114 block->getArgument(it.index())))
1115 return yieldOp->emitError()
1116 << "Yield operand #" << it.index()
1117 << " is not equivalent to the corresponding iter bbArg";
1118 }
1119
1120 return success();
1121 }
1122};
1123
1124/// Bufferization of scf.yield. Bufferized as part of their enclosing ops, so
1125/// this is for analysis only.
1126struct YieldOpInterface
1127 : public BufferizableOpInterface::ExternalModel<YieldOpInterface,
1128 scf::YieldOp> {
1129 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
1130 const AnalysisState &state) const {
1131 return true;
1132 }
1133
1134 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
1135 const AnalysisState &state) const {
1136 return false;
1137 }
1138
1139 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
1140 const AnalysisState &state) const {
1141 if (auto ifOp = dyn_cast<scf::IfOp>(op->getParentOp())) {
1142 return {{op->getParentOp()->getResult(opOperand.getOperandNumber()),
1143 BufferRelation::Equivalent, /*isDefinite=*/false}};
1144 }
1145 if (isa<scf::ExecuteRegionOp>(op->getParentOp()))
1146 return {{op->getParentOp()->getResult(opOperand.getOperandNumber()),
1147 BufferRelation::Equivalent}};
1148 return {};
1149 }
1150
1151 bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand,
1152 const AnalysisState &state) const {
1153 // Yield operands always bufferize inplace. Otherwise, an alloc + copy
1154 // may be generated inside the block. We should not return/yield allocations
1155 // when possible.
1156 return true;
1157 }
1158
1159 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
1160 const BufferizationOptions &options,
1161 BufferizationState &state) const {
1162 auto yieldOp = cast<scf::YieldOp>(op);
1163 if (!isa<scf::ExecuteRegionOp, scf::IfOp, scf::IndexSwitchOp, scf::ForOp,
1164 scf::WhileOp>(yieldOp->getParentOp()))
1165 return yieldOp->emitError("unsupported scf::YieldOp parent");
1166
1167 SmallVector<Value> newResults;
1168 for (const auto &it : llvm::enumerate(yieldOp.getResults())) {
1169 Value value = it.value();
1170 if (isa<TensorLikeType>(value.getType())) {
1171 FailureOr<Value> maybeBuffer =
1172 getBuffer(rewriter, value, options, state);
1173 if (failed(maybeBuffer))
1174 return failure();
1175 Value buffer = *maybeBuffer;
1176 // We may have to cast the value before yielding it.
1177 if (isa<scf::ForOp, scf::IfOp, scf::IndexSwitchOp>(
1178 yieldOp->getParentOp())) {
1179 FailureOr<BufferLikeType> resultType = bufferization::getBufferType(
1180 yieldOp->getParentOp()->getResult(it.index()), options, state);
1181 if (failed(resultType))
1182 return failure();
1183 buffer = castBuffer(rewriter, buffer, *resultType, options);
1184 } else if (auto whileOp =
1185 dyn_cast<scf::WhileOp>(yieldOp->getParentOp())) {
1186 FailureOr<BufferLikeType> resultType = bufferization::getBufferType(
1187 whileOp.getBeforeArguments()[it.index()], options, state);
1188 if (failed(resultType))
1189 return failure();
1190 buffer = castBuffer(rewriter, buffer, *resultType, options);
1191 }
1192 newResults.push_back(buffer);
1193 } else {
1194 newResults.push_back(value);
1195 }
1196 }
1197
1198 replaceOpWithNewBufferizedOp<scf::YieldOp>(rewriter, op, newResults);
1199 return success();
1200 }
1201};
1202
1203/// Bufferization of ForallOp. This also bufferizes the terminator of the
1204/// region. There are op interfaces for the terminators (InParallelOp
1205/// and ParallelInsertSliceOp), but these are only used during analysis. Not
1206/// for bufferization.
1207struct ForallOpInterface
1208 : public BufferizableOpInterface::ExternalModel<ForallOpInterface,
1209 ForallOp> {
1210 bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand,
1211 const AnalysisState &state) const {
1212 // All tensor operands to `scf.forall` are `shared_outs` and all
1213 // shared outs are assumed to be read by the loop. This does not
1214 // account for the case where the entire value is over-written,
1215 // but being conservative here.
1216 return true;
1217 }
1218
1219 bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand,
1220 const AnalysisState &state) const {
1221 // Outputs of scf::ForallOps are always considered as a write.
1222 return true;
1223 }
1224
1225 AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand,
1226 const AnalysisState &state) const {
1227 auto forallOp = cast<ForallOp>(op);
1228 return {
1229 {{forallOp.getTiedOpResult(&opOperand), BufferRelation::Equivalent}}};
1230 }
1231
1232 bool isWritable(Operation *op, Value value,
1233 const AnalysisState &state) const {
1234 return true;
1235 }
1236
1237 LogicalResult bufferize(Operation *op, RewriterBase &rewriter,
1238 const BufferizationOptions &options,
1239 BufferizationState &state) const {
1240 OpBuilder::InsertionGuard guard(rewriter);
1241 auto forallOp = cast<ForallOp>(op);
1242 int64_t rank = forallOp.getRank();
1243
1244 // Get buffers for all output operands.
1245 SmallVector<Value> buffers;
1246 for (Value out : forallOp.getOutputs()) {
1247 FailureOr<Value> buffer = getBuffer(rewriter, out, options, state);
1248 if (failed(buffer))
1249 return failure();
1250 buffers.push_back(*buffer);
1251 }
1252
1253 // Use buffers instead of block arguments.
1254 rewriter.setInsertionPointToStart(forallOp.getBody());
1255 for (const auto &it : llvm::zip(
1256 forallOp.getBody()->getArguments().drop_front(rank), buffers)) {
1257 BlockArgument bbArg = std::get<0>(it);
1258 Value buffer = std::get<1>(it);
1259 Value bufferAsTensor = ToTensorOp::create(rewriter, forallOp.getLoc(),
1260 bbArg.getType(), buffer);
1261 bbArg.replaceAllUsesWith(bufferAsTensor);
1262 }
1263
1264 // Create new ForallOp without any results and drop the automatically
1265 // introduced terminator.
1266 rewriter.setInsertionPoint(forallOp);
1267 ForallOp newForallOp;
1268 newForallOp = ForallOp::create(
1269 rewriter, forallOp.getLoc(), forallOp.getMixedLowerBound(),
1270 forallOp.getMixedUpperBound(), forallOp.getMixedStep(),
1271 /*outputs=*/ValueRange(), forallOp.getMapping());
1272
1273 // Keep discardable attributes from the original op.
1274 newForallOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1275
1276 rewriter.eraseOp(newForallOp.getBody()->getTerminator());
1277
1278 // Move over block contents of the old op.
1279 SmallVector<Value> replacementBbArgs;
1280 replacementBbArgs.append(newForallOp.getBody()->getArguments().begin(),
1281 newForallOp.getBody()->getArguments().end());
1282 replacementBbArgs.append(forallOp.getOutputs().size(), Value());
1283 rewriter.mergeBlocks(forallOp.getBody(), newForallOp.getBody(),
1284 replacementBbArgs);
1285
1286 // Remove the old op and replace all of its uses.
1287 replaceOpWithBufferizedValues(rewriter, op, buffers);
1288
1289 return success();
1290 }
1291
1292 FailureOr<BufferLikeType>
1293 getBufferType(Operation *op, Value value, const BufferizationOptions &options,
1294 const BufferizationState &state,
1295 SmallVector<Value> &invocationStack) const {
1296 auto forallOp = cast<ForallOp>(op);
1297
1298 if (auto bbArg = dyn_cast<BlockArgument>(value))
1299 // A tensor block argument has the same bufferized type as the
1300 // corresponding output operand.
1301 return bufferization::getBufferType(
1302 forallOp.getTiedOpOperand(bbArg)->get(), options, state,
1303 invocationStack);
1304
1305 // The bufferized result type is the same as the bufferized type of the
1306 // corresponding output operand.
1307 return bufferization::getBufferType(
1308 forallOp.getOutputs()[cast<OpResult>(value).getResultNumber()], options,
1309 state, invocationStack);
1310 }
1311
1312 bool isRepetitiveRegion(Operation *op, unsigned index) const {
1313 auto forallOp = cast<ForallOp>(op);
1314
1315 // This op is repetitive if it has 1 or more steps.
1316 // If the control variables are dynamic, it is also considered so.
1317 for (auto [lb, ub, step] :
1318 llvm::zip(forallOp.getMixedLowerBound(), forallOp.getMixedUpperBound(),
1319 forallOp.getMixedStep())) {
1320 std::optional<int64_t> lbConstant = getConstantIntValue(lb);
1321 if (!lbConstant)
1322 return true;
1323
1324 std::optional<int64_t> ubConstant = getConstantIntValue(ub);
1325 if (!ubConstant)
1326 return true;
1327
1328 std::optional<int64_t> stepConstant = getConstantIntValue(step);
1329 if (!stepConstant)
1330 return true;
1331
1332 if (*lbConstant + *stepConstant < *ubConstant)
1333 return true;
1334 }
1335 return false;
1336 }
1337
1338 bool isParallelRegion(Operation *op, unsigned index) const {
1339 return isRepetitiveRegion(op, index);
1340 }
1341};
1342
1343/// Nothing to do for InParallelOp.
1344struct InParallelOpInterface
1345 : public BufferizableOpInterface::ExternalModel<InParallelOpInterface,
1346 InParallelOp> {
1347 LogicalResult bufferize(Operation *op, RewriterBase &b,
1348 const BufferizationOptions &options,
1349 BufferizationState &state) const {
1350 llvm_unreachable("op does not have any tensor OpOperands / OpResults");
1351 return failure();
1352 }
1353};
1354
1355} // namespace
1356} // namespace scf
1357} // namespace mlir
1358
1360 DialectRegistry &registry) {
1361 registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) {
1362 ConditionOp::attachInterface<ConditionOpInterface>(*ctx);
1363 ExecuteRegionOp::attachInterface<ExecuteRegionOpInterface>(*ctx);
1364 ForOp::attachInterface<ForOpInterface>(*ctx);
1365 IfOp::attachInterface<IfOpInterface>(*ctx);
1366 IndexSwitchOp::attachInterface<IndexSwitchOpInterface>(*ctx);
1367 ForallOp::attachInterface<ForallOpInterface>(*ctx);
1368 InParallelOp::attachInterface<InParallelOpInterface>(*ctx);
1369 WhileOp::attachInterface<WhileOpInterface>(*ctx);
1370 YieldOp::attachInterface<YieldOpInterface>(*ctx);
1371 });
1372}
return success()
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
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:95
BlockArgument getArgument(unsigned i)
Definition Block.h:139
unsigned getNumArguments()
Definition Block.h:138
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:433
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:414
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:526
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:233
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:717
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:307
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122