| //===-- BufferizableOpInterface.td - Bufferizable Ops ------*- tablegen -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef BUFFERIZABLE_OP_INTERFACE |
| #define BUFFERIZABLE_OP_INTERFACE |
| |
| include "mlir/IR/OpBase.td" |
| |
| def BufferizableOpInterface : OpInterface<"BufferizableOpInterface"> { |
| let description = [{ |
| An op interface for One-Shot Bufferize. Ops that implement this interface |
| interface can be analyzed and bufferized using One-Shot Bufferize. |
| |
| Note: All "bufferizesTo*" and "getAliasing*" interface methods must be |
| implemented conservatively. If it is not statically known whether an |
| OpOperand/Value bufferizes in a certain way (e.g., to a memory write), |
| the worst case must be assumed (e.g., that it does). Similarly, |
| "getAliasing*" interface methods may always return additional OpOperands or |
| Values, but must not miss an OpOperand or Value that could potentially |
| alias at runtime. |
| }]; |
| let cppNamespace = "::mlir::bufferization"; |
| let methods = [ |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given Value may bufferize to a new buffer |
| allocation. If it is statically unknown that the given Value |
| bufferizes to a buffer allocation, `true` should be returned. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"bufferizesToAllocation", |
| /*args=*/(ins "::mlir::Value":$value), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/"return false;" |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given OpOperand bufferizes to a memory read. This |
| method will never be called on OpOperands that do not have a tensor |
| type. |
| |
| Note: It is always safe to consider an OpOperand as a memory read, |
| even if it does actually not read; however, this can introduce |
| unnecessary out-of-place bufferization decisions. One-Shot Analysis |
| considers OpOperands of unknown ops (that do not implement this |
| interface) as reading OpOperands. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"bufferizesToMemoryRead", |
| /*args=*/(ins "::mlir::OpOperand &":$opOperand, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| // Does not have to be implemented for ops without tensor OpOperands. |
| llvm_unreachable("bufferizesToMemoryRead not implemented"); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given OpOperand bufferizes to a memory write. |
| |
| This method will never be called on OpOperands that do not have a |
| tensor type. |
| |
| This method will never be called on OpOperands that do not have an |
| aliasing Value. Intuitively, it does not make sense for an OpOperand |
| to bufferize to a memory write without returning an aliasing tensor, |
| because the write would have no visible effect outside of the op. |
| |
| Note: It is always safe to consider an OpOperand as a memory write, |
| even if it does actually not write; however, this can introduce |
| unnecessary out-of-place bufferization decisions. One-Shot Analysis |
| considers OpOperands of unknown ops (that do not implement this |
| interface) as writing OpOperands. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"bufferizesToMemoryWrite", |
| /*args=*/(ins "::mlir::OpOperand &":$opOperand, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| // Does not have to be implemented for ops without tensor OpOperands. |
| // Does not have to be implemented for OpOperands that do not have an |
| // aliasing Value. |
| llvm_unreachable("bufferizesToMemoryWrite not implemented"); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the operation bufferizes to IR that performs only |
| element-wise accesses on the specified tensor operands. (The operands |
| must have the same shape.) The `bufferize` method must be implemented |
| in such a way that it is free of loop-carried dependences. I.e., all |
| loads at a position appear before all stores at the same position. |
| |
| Example: Consider a hypothetical op element-wise op, where the "ins" |
| bufferize to a memory read and the "outs" bufferize to a memory write. |
| ``` |
| test.element_wise ins(%0), outs(%1) : tensor<3xf32> |
| ``` |
| |
| The following is a valid access pattern: |
| ``` |
| load(%0[1]) |
| store(%1[1]) |
| load(%0[2]) |
| store(%1[2]) |
| load(%0[0]) |
| store(%1[0]) |
| ``` |
| |
| The following would be an invalid (not element-wise) access pattern: |
| ``` |
| load(%0[1]) |
| store(%0[1]) |
| load(%0[1]) |
| ... |
| ``` |
| |
| Element-wise ops can sometimes bufferize more efficiently: a RaW |
| conflict between two operands of the same op can be avoided if it is |
| guaranteed that an original element value is no longer needed after |
| writing a computed element value at the same location. E.g., such an |
| optimization is possible in the above example if %0 and %1 are |
| equivalent tensors. (It is not possible, if %0 and %1 are merely |
| aliasing. It is not necessary if %0 and %1 are not aliasing at all, |
| because there would be no conflict anyway.) |
| |
| Note: Tensor operands that are not included in `opOperands` can be |
| ignored. A conservative implementation of this interface method may |
| always return "false". |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"bufferizesToElementwiseAccess", |
| /*args=*/(ins "const ::mlir::bufferization::AnalysisState &":$state, |
| "ArrayRef<OpOperand *>":$opOperands), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| // It is always safe to assume that the op is not element-wise. |
| return false; |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given OpResult bufferizes to a memory write. |
| This is the same property as `bufferizesToMemoryWrite`, but from The |
| perspective of OpResults. |
| |
| This method will never be called on OpResults that do not have a |
| tensor type. |
| |
| This method has a default implementation. By default, it returns |
| `true` if any of the following three cases applies. |
| |
| 1. There is no corresponding aliasing OpOperand. |
| |
| Example: `tensor.generate ... : tensor<10xf32>` |
| The op fills a newly allocated buffer and bufferizes to a memory |
| write. |
| |
| Counter-example: bufferization.alloc_tensor |
| The op just allocates and does not specifiy the data of the tensor, |
| so resultBufferizesToMemoryWrite is overridden to return false. |
| |
| 2. At least one aliasing OpOperand bufferizes to a memory write. |
| |
| Example: `tensor.insert %f into %t[...] : tensor<?xf32>` |
| The destination OpOperand bufferizes to a memory write, so the |
| result also bufferizes to a memory write. |
| |
| 3. At least one aliasing OpOperand's value is defined inside the |
| defining op of the given OpResult and it is a memory write. |
| |
| According to this rule, an aliasing OpOperand value that is defined |
| inside this op and is bufferizing to a memory write makes the given |
| OpResult bufferize to a memory write. |
| |
| Example: |
| ``` |
| %r = scf.if ... -> tensor<?xf32> { |
| %1 = tensor.insert %f into %t[...] : tensor<?xf32> |
| scf.yield %1 : tensor<?xf32> |
| } else { ... } |
| ``` |
| The scf.if result bufferizes to a memory write because %1 (an |
| OpResult defined inside the scf.if op) bufferizes to a memory |
| write. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"resultBufferizesToMemoryWrite", |
| /*args=*/(ins "::mlir::OpResult":$opResult, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| assert(opResult.getDefiningOp() == $_op.getOperation() && |
| "invalid OpResult"); |
| return ::mlir::bufferization::detail::defaultResultBufferizesToMemoryWrite( |
| opResult, state); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given OpOperand must bufferize in-place. Alias |
| sets and inplace attributes will be set up accordingly before making |
| any other bufferization decisions. This method will never be called on |
| OpOperands that do not have a tensor type. |
| |
| Note: Unranked tensor OpOperands always bufferize in-place. This could |
| be extended in the future. Unranked tensors are used with external |
| functions only. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"mustBufferizeInPlace", |
| /*args=*/(ins "::mlir::OpOperand &":$opOperand, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return ::llvm::isa<::mlir::UnrankedTensorType>(opOperand.get().getType()); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return the Values that may alias with a given OpOperand when |
| bufferized in-place. This method will never be called on OpOperands |
| that do not have a tensor type. |
| |
| This method can return multiple Values, indicating that a given |
| OpOperand may at runtime alias with any (or multiple) of the returned |
| Values. |
| |
| Each alias is specified with a degree of certainty: |
| |
| * MAYBE (`isDefinite = false`): At runtime, buffer(opOperand) may |
| alias with the specified Value. |
| * DEFINITE (`isDefinite = true`, default): At runtime, |
| buffer(opOperand) is guaranteed to alias the buffer of the specified |
| Value. This is a stronger property than MAYBE and allows for more |
| precise analyses. DEFINITE properties should be used when possible. |
| |
| Furthermore, each alias is specified with a buffer relation: |
| |
| * `BufferRelation::Equivalent`: Both aliases are the exact same |
| buffer. I.e., same size, no offset, same strides. |
| * `BufferRelation::Unknown`: There is no further information apart |
| from the fact that both buffers alias. |
| |
| False positives are allowed in the list of Values, but they can |
| adversely affect the accuracy of the anlysis. On the contrary, |
| omitting potential aliases is incorrect. |
| |
| One possible (conservative) implementation of this interface method, |
| that is always safe, is to return all tensor Values with |
| BufferRelation::Unknown and MAYBE. |
| |
| Examples: |
| |
| ``` |
| // aliasingValues(%t) = DEFINITE {Equivalent %r} |
| %r = tensor.insert_slice %f into %t : tensor<10xf32> |
| |
| // aliasingValues(%t) = DEFINITE {Unknown %r} |
| // Note: "Buffer is subset of buffer" relationship are not yet |
| // supported, so "Unknown" is the best we can do for now. |
| %r = tensor.extract_slice %t[0]][5][1] |
| : tensor<10xf32> to tensor<5xf32> |
| |
| // aliasingValues(%t1) = MAYBE {Equivalent %r} |
| // aliasingValues(%t2) = MAYBE {Equivalent %r} |
| %r = arith.select %c, %t1, %t2 : tensor<10xf32> |
| |
| // A hypothetical op that bufferizes to rolling a dice and based on |
| // the result to either return buffer(%t) or a newly allocated copy |
| // thereof. |
| // aliasingValues(%t) = MAYBE {Equivalent %r} |
| %r = "dummy.alias_or_copy(%t) : (tensor<10xf32>) -> (tensor<10xf32>)" |
| ``` |
| }], |
| /*retType=*/"::mlir::bufferization::AliasingValueList", |
| /*methodName=*/"getAliasingValues", |
| /*args=*/(ins "::mlir::OpOperand &":$opOperand, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| // Does not have to be implemented for ops without tensor OpOperands. |
| assert(::llvm::isa<::mlir::TensorType>(opOperand.get().getType()) && |
| "expected OpOperand with tensor type"); |
| llvm_unreachable("getAliasingValues not implemented"); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return the OpOperands that alias with a given Value when bufferized |
| in-place. This method will never be called on Values that do not |
| have a tensor type. |
| |
| By default, this method is the inverse of `getAliasingValues`. Ops |
| with a region that yield values may want to override this method to |
| return the OpOperands that are yielded by the terminator. |
| |
| This method can return multiple OpOperands, indicating that a given |
| Value may at runtime alias with any (or multiple) of the returned |
| OpOperands. |
| |
| This property is specified with a degree of certainty: |
| |
| * MAYBE (`isDefinite = false`): At runtime, buffer(value) may alias |
| with the specified OpOperand. |
| * DEFINITE (`isDefinite = true`, default): At runtime, |
| buffer(value) is guaranteed to alias the buffer of the specified |
| OpOperand. This is a stronger property than MAYBE and allows for |
| more precise analyses. DEFINITE properties should be used when |
| possible. |
| |
| For each alias, a BufferRelation can be specified: |
| |
| * `BufferRelation::Equivalent`: Both aliases are the exact same |
| buffer. I.e., same size, no offset, same strides. |
| * `BufferRelation::Unknown`: There is no further information apart |
| from the fact that both buffers alias. |
| |
| False positives are allowed in the list of OpOperands, but they can |
| adversely affect the accuracy of the anlysis. On the contrary, |
| omitting potential aliases is incorrect. |
| |
| One possible (conservative) implementation of this interface method, |
| that is always safe, is to return all tensor OpOperands with |
| BufferRelation::Unknown and MAYBE. |
| |
| Note: If the returned list of OpOperands is empty, this op definitely |
| bufferizes to a new allocation. In that case `bufferizesToAllocation` |
| must return `true`. |
| |
| Examples: |
| |
| ``` |
| // aliasingOpOperands(%r) = DEFINITE {Equivalent %t} |
| %r = tensor.insert_slice %f into %t : tensor<10xf32> |
| |
| // aliasingOpOperands(%r) = DEFINITE {Unknown %t} |
| %r = tensor.extract_slice %t[0]][5][1] |
| : tensor<10xf32> to tensor<5xf32> |
| |
| // aliasingOpOperands(%r) = DEFINITE {Equivalent %t1, Equivalent %t2} |
| %r = arith.select %c, %t1, %t2 : tensor<10xf32> |
| |
| // aliasingOpOperands(%r) = MAYBE {} |
| %r = tensor.empty() : tensor<10xf32> |
| ``` |
| }], |
| /*retType=*/"::mlir::bufferization::AliasingOpOperandList", |
| /*methodName=*/"getAliasingOpOperands", |
| /*args=*/(ins "::mlir::Value":$value, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| assert(isa<::mlir::TensorType>(value.getType()) && |
| "expected tensor type"); |
| return ::mlir::bufferization::detail::defaultGetAliasingOpOperands( |
| value, state); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Resolve all inplacability conflicts by inserting explicit |
| `bufferization.alloc_tensor` ops. Examples of inplacability conflicts |
| are read-after-write conflicts or writes into non-writable buffers. |
| |
| This method should rewrite the IR in such a way that for each tensor |
| OpOperand t, buffer(t) can be directly used when during bufferization. |
| The bufferization does no longer have to care about inplacability |
| conflicts. |
| |
| This method can query analysis information from the given analysis |
| state. |
| }], |
| /*retType=*/"::mlir::LogicalResult", |
| /*methodName=*/"resolveConflicts", |
| /*args=*/(ins "::mlir::RewriterBase &":$rewriter, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| auto bufferizableOp = |
| ::llvm::cast<BufferizableOpInterface>($_op.getOperation()); |
| return bufferizableOp.resolveTensorOpOperandConflicts( |
| rewriter, state); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Bufferize this op, i.e., rewrite it into a memref-based equivalent. |
| Buffers of tensor SSA values can be retrieved via `getBuffer`. |
| Uses of tensor results of the existing tensor op can be replaced with |
| `replaceOpWithBufferizedValues` or `replaceOpWithNewBufferizedOp`. |
| These two functions automatically handle the tensor-to-memref type |
| conversion. |
| |
| The implementation of this method must be consistent with the |
| remaining methods, in particular `getAliasingOpOperands`. I.e., a |
| tensor result `r` may only be replaced with: |
| |
| a) One of the buffers in getAliasingOpOperands(r). |
| b) Or: A newly allocated buffer (only if `bufferizesToAllocation`). |
| |
| This method will never be called on ops that do not have at least one |
| tensor operand/result. |
| |
| The return value of this method indicates whether there was an error |
| while bufferizing this op (such as failing to create a new buffer |
| allocation op). The bufferization driver immediately stops bufferizing |
| the input IR and returns `failure` in that case. If this op is |
| expected to survive bufferization, `success` should be returned |
| (together with `allow-unknown-ops` enabled). |
| |
| Note: If this op supports unstructured control flow in its regions, |
| then this function should also bufferize all block signatures that |
| belong to this op. Branch ops (that branch to a block) are typically |
| bufferized together with the block signature (this is just a |
| suggestion to make sure IR is valid at every point in time and could |
| be done differently). |
| }], |
| /*retType=*/"::mlir::LogicalResult", |
| /*methodName=*/"bufferize", |
| /*args=*/(ins "::mlir::RewriterBase &":$rewriter, |
| "const ::mlir::bufferization::BufferizationOptions &":$options), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| llvm_unreachable("bufferize not implemented"); |
| return ::mlir::failure(); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given Value can be written to in-place. Value is |
| either an OpResult of this operation or a BlockArgument of a block of |
| this operation. |
| |
| Most OpResult buffers can be written to, but some ops such as |
| ConstantOp may bufferize to non-writable (read-only) memory locations. |
| Therefore, by default, this method returns `true` for OpResults. This |
| method will never be called on OpResults that do not have a tensor |
| type. |
| |
| Whether a BlockArgument can be written to or not depends on the |
| operation. This method conservatively returns `false`. This method |
| will never be called on BlockArguments that do not have a tensor type. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"isWritable", |
| /*args=*/(ins "::mlir::Value":$value, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return ::llvm::isa<::mlir::OpResult>(value); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the `uRead` and `uWrite` do not constitute a RaW |
| conflict. If they are conflicting or if it is unknown whether they are |
| conflicting, return `false`. This method will never be called with |
| OpOperands that do not have a tensor type. At least one of the two |
| given OpOperands belongs to this operation. |
| |
| This method can be implemented to specify custom RaW analysis rules. |
| If this method returns `true` the given OpOperands are not considered |
| to be conflicting and do not force out-of-place bufferization. (There |
| may still be other conflicts that do.) |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"isNotConflicting", |
| /*args=*/(ins "::mlir::OpOperand *":$uRead, |
| "::mlir::OpOperand *":$uWrite, |
| "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return false; |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `failure` if this op does not pass the analysis. This method |
| is run during One-Shot Bufferize (after all post-analysis steps). If |
| the op does not pass the analysis, bufferization is aborted. |
| |
| This method can be used to check expected invariants and limitations |
| of the current bufferization implementation. |
| }], |
| /*retType=*/"::mlir::LogicalResult", |
| /*methodName=*/"verifyAnalysis", |
| /*args=*/(ins "const ::mlir::bufferization::AnalysisState &":$state), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return ::mlir::success(); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return the bufferized type of the given tensor value (without |
| bufferizing the IR). The value is either a BlockArgument of a block |
| that belongs to this op or an OpResult of the given op. |
| |
| This method is useful when the bufferized type of value must be |
| predicted before modifying any IR. |
| |
| Implementations may call `bufferization::getBufferType` to compute the |
| bufferized type of another SSA value. The same (unmodified) |
| `invocationStack` must be passed to that function. The stack contains |
| all SSA values for which a buffer type computation is currently in |
| progress. Implementations may inspect the stack to detect repetitive |
| computations for the same SSA value. (E.g., when bufferized types of a |
| loop.) |
| |
| Note: This interface method should never be called directly from user |
| code. Always use `bufferization::getBufferType`. |
| }], |
| /*retType=*/"::mlir::FailureOr<::mlir::BaseMemRefType>", |
| /*methodName=*/"getBufferType", |
| /*args=*/(ins "::mlir::Value":$value, |
| "const ::mlir::bufferization::BufferizationOptions &":$options, |
| "::llvm::SmallVector<::mlir::Value> &":$invocationStack), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| assert(getOwnerOfValue(value) == $_op.getOperation() && |
| "expected that value belongs to this op"); |
| assert(invocationStack.back() == value && |
| "inconsistant invocation stack"); |
| return ::mlir::bufferization::detail::defaultGetBufferType( |
| value, options, invocationStack); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given region of this op is repetitive. By default |
| this information is queried from the `RegionBranchOpInterface`. Ops |
| that do not implement this inferface can override this method to |
| declare regions as repetitive. |
| |
| The RaW conflict detection of One-Shot Analysis is more strict inside |
| repetitive regions: Op dominance cannot always be used to rule out |
| certain potential conflicts (e.g., a conflicting write happening after |
| a read), because there may not be a meaningful ordering of certain ops |
| that are executed multiple times. This is described in more detail in |
| documentation of One-Shot Analysis. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"isRepetitiveRegion", |
| /*args=*/(ins "unsigned":$index), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return ::mlir::bufferization::detail::defaultIsRepetitiveRegion( |
| ::llvm::cast<BufferizableOpInterface>($_op.getOperation()), index); |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the given region of this op is parallel, i.e., |
| multiple instances of the region may be executing at the same time. |
| If a region is parallel, it must also be marked as "repetitive". |
| |
| The RaW conflict detection of One-Shot Analysis is more strict inside |
| parallel regions: Buffer may have to be privatized. |
| |
| By default, regions are assumed to be sequential. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"isParallelRegion", |
| /*args=*/(ins "unsigned":$index), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return false; |
| }] |
| >, |
| InterfaceMethod< |
| /*desc=*/[{ |
| Return "true" if the this op has tensor semantics and should be |
| bufferized. By default, ops with tensor operands, tensor op results |
| and/or tensor block arguments have tensor semantics. |
| |
| This interface methods can be implemented by ops that should be |
| bufferized but do not have tensor semantics according to the above |
| definition. E.g., this function can return "true" for symbols. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"hasTensorSemantics", |
| /*args=*/(ins), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return ::mlir::bufferization::detail |
| ::defaultHasTensorSemantics($_op.getOperation()); |
| }] |
| >, |
| StaticInterfaceMethod< |
| /*desc=*/[{ |
| Return `true` if the op and this interface implementation supports |
| unstructured control flow. I.e., regions with multiple blocks. This is |
| not supported in most ops, so the default answer is `false`. |
| }], |
| /*retType=*/"bool", |
| /*methodName=*/"supportsUnstructuredControlFlow", |
| /*args=*/(ins), |
| /*methodBody=*/"", |
| /*defaultImplementation=*/[{ |
| return false; |
| }] |
| >, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| /// Resolve out-of-place tensor OpOperands with explicit allocations in the |
| /// form of `bufferization.alloc_tensor` ops. |
| ::mlir::LogicalResult resolveTensorOpOperandConflicts( |
| ::mlir::RewriterBase &rewriter, |
| const ::mlir::bufferization::AnalysisState &state); |
| |
| /// Return `true` if the given OpOperand creates an alias but does neither |
| /// read nor write. This implies that `bufferizesToMemoryRead` and |
| /// `bufferizesToMemoryWrite` must return `false`. This method will never |
| /// be called on OpOperands that do not have a tensor type. |
| /// |
| /// Examples of such ops are `tensor.extract_slice` and `tensor.cast`. |
| bool bufferizesToAliasOnly( |
| ::mlir::OpOperand &opOperand, |
| const ::mlir::bufferization::AnalysisState &state) { |
| auto bufferizableOp = |
| ::llvm::cast<::mlir::bufferization::BufferizableOpInterface>(getOperation()); |
| return !bufferizableOp.bufferizesToMemoryRead(opOperand, state) |
| && !bufferizableOp.bufferizesToMemoryWrite(opOperand, state) |
| && bufferizableOp.getAliasingValues(opOperand, state) |
| .getNumAliases() != 0; |
| } |
| }]; |
| } |
| |
| #endif // BUFFERIZABLE_OP_INTERFACE |