llvm/mlir/include/mlir/Dialect/Bufferization/IR/BufferizableOpInterface.td

//===-- 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 specify 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=*/"::llvm::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=*/"::llvm::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=*/"::llvm::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.
    ::llvm::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