//===-- SubsetOpInterface.td - Tensor Subsets --------------*- 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 SUBSET_OP_INTERFACE #define SUBSET_OP_INTERFACE include "mlir/IR/OpBase.td" def SubsetOpInterface : OpInterface<"SubsetOpInterface"> { let description = [{ This interface can be implemented by ops that operate on tensor subsets. A "subset" is a part of a tensor. This interface describes the subset that an implementing op operates on. Only the specified subset may be accessed by the op. Subset ops come in two flavours and ops that implement the `SubsetOpInterface` must also implement one of the respective interfaces. - Insertion flavor: Ops that insert a source value into a destination tensor at the specified subset. Such ops return an updated destination tensor and usually implement the `DestinationStyleOpInterface`. Insertion ops must implement the `SubsetInsertionOpInterface`. - Extraction flavor: Ops that extract at a subset. Extraction ops must implement the `SubsetExtractionOpInterface`. How the subset is specified is up to the implementing op. E.g.: - `tensor.extract_slice/insert_slice` describe the subset as a hyperrectangular slice. - `tensor.gather/scatter` describe the subset as list of indices. (Not implemented yet.) }]; let cppNamespace = "::mlir"; let methods = [ InterfaceMethod< /*desc=*/[{ Return "true" if this op and the given candidate subset op operate on equivalent subsets. Return "false" if the two subsets are disjoint or cannot be proven to be equivalent. This interface method does not have to be implemented if `getAccessedHyperrectangularSlice` is implemented. }], /*retType=*/"bool", /*methodName=*/"operatesOnEquivalentSubset", /*args=*/(ins "::mlir::SubsetOpInterface":$candidate, "::llvm::function_ref":$equivalenceFn), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::detail::defaultOperatesOnEquivalentSubset( $_op, candidate, equivalenceFn); }] >, InterfaceMethod< /*desc=*/[{ Return "true" if this op and the given candidate subset op operate on disjoint subsets. Return "false" if the two subsets are equivalent, overlapping or cannot be proven to be disjoint. This interface method does not have to be implemented if `getAccessedHyperrectangularSlice` is implemented. }], /*retType=*/"bool", /*methodName=*/"operatesOnDisjointSubset", /*args=*/(ins "::mlir::SubsetOpInterface":$candidate, "::llvm::function_ref":$equivalenceFn), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::detail::defaultOperatesOnDisjointSubset( $_op, candidate, equivalenceFn); }] >, InterfaceMethod< /*desc=*/[{ If this op operates on a hyperrectangular subset, return a description of the subset in terms of offsets, sizes and strides. Otherwise, return "failure". This interface method is a convenience method for the most common case of hyperrectangular subset ops. It is optional. If it is implemented, `operatesOnEquivalentSubset` and `operatesOnDisjointSubset` do not have to be implemented. }], /*retType=*/"::mlir::FailureOr<::mlir::HyperrectangularSlice>", /*methodName=*/"getAccessedHyperrectangularSlice", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::failure(); }] >, ]; let verify = [{ return ::mlir::detail::verifySubsetOpInterface( ::mlir::cast<::mlir::SubsetOpInterface>($_op)); }]; let extraClassDeclaration = [{ /// Return the container that this operation is operating on. In case of an /// extraction op, the container is the source tensor. In case of an /// insertion op, the container is the destination tensor. Value getTensorContainer() { return ::mlir::detail::getTensorContainer(getOperation()); } }]; } def SubsetExtractionOpInterface : OpInterface<"SubsetExtractionOpInterface", [SubsetOpInterface]> { let description = [{ This interface can be implemented by ops that extract a value from a source tensor at a specified subset. The elements in the source tensor that are read by this extraction are called "subset". Extraction ops must have a single result value. }]; let cppNamespace = "::mlir"; let methods = [ InterfaceMethod< /*desc=*/[{ Return the source tensor operand. }], /*retType=*/"::mlir::OpOperand &", /*methodName=*/"getSourceOperand", /*args=*/(ins) >, ]; let verify = [{ return ::mlir::detail::verifySubsetExtractionOpInterface( ::mlir::cast<::mlir::SubsetExtractionOpInterface>($_op)); }]; let extraClassDeclaration = [{ /// Return the single result of this op. ::mlir::Value getResult() { return getOperation()->getResult(0); } }]; } def SubsetInsertionOpInterface : OpInterface<"SubsetInsertionOpInterface", [SubsetOpInterface]> { let description = [{ This interface can be implemented by ops that insert a source value into a destination tensor at a specified subset. The elements in the destination tensor that are overwritten by this insertion are called "subset". The updated destination tensor is returned. This interface provides helper methods for efficient bufferization of subset-based tensor IR. Tensor subsets can bufferize to buffer "views"/ "aliases" (in contrast to one or multiple less efficient buffer allocation). This interface is queried by One-Shot Bufferize to detect cases where a seeming read-after-write is not actually a conflict because the respective ops are operating on equivalent subsets. More details can be found in the documentation of One-Shot Analysis (see `areNonConflictingSubsets`). }]; let cppNamespace = "::mlir"; let methods = [ InterfaceMethod< /*desc=*/[{ Return the source operand. The source operand can have any type. }], /*retType=*/"::mlir::OpOperand &", /*methodName=*/"getSourceOperand", /*args=*/(ins) >, InterfaceMethod< /*desc=*/[{ Return the destination operand. The destination operand must be a tensor. This function does not have to be implemented for destination style ops that have exactly one "init" operand. }], /*retType=*/"::mlir::OpOperand &", /*methodName=*/"getDestinationOperand", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::detail::defaultGetDestinationOperand( $_op.getOperation()); }] >, InterfaceMethod< /*desc=*/[{ Return the updated destination result. This function does not have to be implemented for destination style ops. }], /*retType=*/"::mlir::OpResult", /*methodName=*/"getUpdatedDestination", /*args=*/(ins), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::detail::defaultGetUpdatedDestination( $_op.getOperation()); }] >, InterfaceMethod< /*desc=*/[{ Return "true" if this operation inserts into a subset that is equivalent to the subset defined by `candidate`. Two subsets are "equivalent" and "same" if they can bufferize to the same buffer views/aliases. If they are "equivalent", the tensor IR may be expressed in terms of different SSA values (but they could bufferize to MemRef SSA values that can CSE without breaking correctness). `equivalenceFn` should return "true" if the two given values are equivalent. Example: ``` // The subset of the SubsetInsertionOpInterface op %1 is equivalent to // the subset defined by %2 (but not "same"): %0 = arith.select %c, %t, %t : tensor %1 = tensor.insert_slice %x into %0[0][5][1] : tensor<5xf32> into tensor %2 = tensor.extract_slice %t[0][5][1] : tensor to tensor<5xf32> // The subset of the SubsetInsertionOpInterface op %1 is equivalent to // and "same" as the subset defined by %2. %1 = tensor.insert_slice %x into %t[0][5][1] : tensor<5xf32> into tensor %2 = tensor.extract_slice %t[0][5][1] : tensor to tensor<5xf32> ``` Note: By default, this function calls `SubsetOpInterface::operatesOnEquivalentSubset`. }], /*retType=*/"bool", /*methodName=*/"isEquivalentSubset", /*args=*/(ins "::mlir::Value":$candidate, "::llvm::function_ref":$equivalenceFn), /*methodBody=*/"", /*defaultImplementation=*/[{ return ::mlir::detail::defaultIsEquivalentSubset( $_op.getOperation(), candidate, equivalenceFn); }] >, InterfaceMethod< /*desc=*/[{ Return the subset of the destination tensor that this operation inserts into. Example: ``` // SubsetOpInterface op: %0 = tensor.insert_slice %t0 into %t1[%pos][5][1] : tensor<5xf32> into tensor // Subset (built by this function): %1 = tensor.extract_slice %t1[%pos][5][1] : tensor to tensor<5xf32> ``` Note: Implementations do not necessarily have to build new IR. They may return existing SSA values. }], /*retType=*/"::mlir::Value", /*methodName=*/"buildSubsetExtraction", /*args=*/(ins "::mlir::OpBuilder &":$builder, "Location":$loc) >, InterfaceMethod< /*desc=*/[{ Return all SSA values that are needed (i.e., must be in scope) at the insertion of the builder when calling `buildSubsetExtraction`. Users of `buildSubsetExtraction` can use this helper method to find a suitable insertion point. Example: The SSA values needed to build the subset in the example of `buildSubsetExtraction` are %t1 and %pos. }], /*retType=*/"::llvm::SmallVector<::mlir::Value>", /*methodName=*/"getValuesNeededToBuildSubsetExtraction", /*args=*/(ins) >, ]; let extraClassDeclaration = [{ /// Return "true" if this operation inserts into the same subset as defined /// by `candidate`. /// /// Note: This function is useful outside of bufferization, where no tensor /// equivalence information is available. bool isSameSubset(OpResult candidate) { auto subsetOp = cast<::mlir::SubsetInsertionOpInterface>( getOperation()); return subsetOp.isEquivalentSubset( candidate, [](Value v1, Value v2) { return v1 == v2; }); } }]; } #endif // SUBSET_OP_INTERFACE