[RFC] DestinationStyleOpInterface
Destination-style operation
A destination-style operation with n
input arguments and m
tensor results is an op
with the following structure
%result:m = dst_op
ins(%in_1:TensorOrScalarType_1, ..., %in_n:TensorOrScalarType_n)
outs(%out_1:TensorType_1, ..., %out_m:TensorType_m)
optional-attrs optional-body
where type(%result_i) == type(%out_i)
. Output tensors out_i
provide “initial” values for the corresponding results.
After bufferization it is transformed into
dst_op ins(%in_1:MemRefOrScalarType_1, ..., %in_n:MemRefOrScalarType_n)
outs(%out_1:MemRefType_1, ..., %out_m:MemRefType_m)
optional-attrs optional-body
Background
LinalgStructuredInterface
in LinalgInterfaces.td contains methods for linalg.generic
and LinalgNamedOps. These methods can be categorized into two types.
-
Methods that handle indexing maps, iterator types, library calls and body regions of Linalg ops
-
Methods relevant for destination-style ops
The reason why both types of methods can be found in LinalgStructuredInterface
is purely historic. In 2020 Linalg added support for ops and transformations on tensors and it became possible to bufferize Linalg operation to themselves, i.e. linalg.generic
with tensor arguments and results gets converted to linalg.generic
with memref arguments an no results.
The class of destination-style operations is wider than linalg.generic
-like ops and it includes LinalgExtOps and GmlStExtensionOps.
In order to improve code sharing, I suggest to move some of the methods in LinalgStructuredInterface
into a separate DestinationStyleOpInterface
within LinalgInterfaces.td.
New Interface
Here is the list of all current methods of LinalgStructuredInterface
. If it is labeled with [MOVED]
then it will be moved to DestinationStyleOpInterface
, if it is labeled with [STAYS]
, then it won’t be affected.
Loop types handling
operations are specific to linalg.generic
.
// Loop types handling.
// [STAYS] Return the number of parallel loops.
unsigned getNumParallelLoops();
// [STAYS] Return the dims that are parallel loops.
void getParallelDims(SmallVectorImpl<unsigned>&);
// [STAYS] Return the number of reduction loops.
unsigned getNumReductionLoops();
// [STAYS] Return the dims that are reduction loops.
void getReductionDims(SmallVectorImpl<unsigned> &);
// [STAYS] Return the number of window loops.
unsigned getNumWindowLoops()
// [STAYS] Return the dims that are window loops.
void getWindowDims(SmallVectorImpl<unsigned> &);
// [STAYS] Return the total number of loops within the current operation.
unsigned getNumLoops();
// [STAYS] Returns true if the current operation has only one loop and
// it's a reduction loop.
bool hasSingleReductionLoop();
Input and output operands handling is defined by the structure of dst-style ops.
// Num input/output arguments handling.
// [MOVES] Return the input shape operands.
ValueRange inputs();
// [MOVES] Return the number of inputs.
int64_t getNumInputs();
// [MOVES] Return the output shape operands.
ValueRange outputs();
// [MOVES] Return the number of outputs.
int64_t getNumOutputs();
// [MOVES] Return the number of inputs and outputs.
int64_t getNumInputsAndOutputs();
// Input operands handling.
// [MOVES] Return the input operands.
OpOperandVector getInputOperands();
// [MOVES] Return the `i`-th input operand.
OpOperand getInputOperand(int64_t);
// [MOVES] Return the subset of input operands that are of buffer type.
OpOperandVector getInputBufferOperands();
// [MOVES] Return the subset of input operands that are of tensor type.
OpOperandVector getInputTensorOperands();
// Output operands handling.
// [MOVES] Return the output operands.
OpOperandVector getOutputOperands();
// [MOVES] Return the `i`-th output operand.
OpOperand* getOutputOperand(int64_t);
// [MOVES] Set the `i`-th output operand.
void setOutputOperand(int64_t":$i, "Value":$value);
// [MOVES] Return the subset of output operands that are of buffer type.
OpOperandVector getOutputBufferOperands();
// [MOVES] Return the subset of output operands that are of tensor type.
OpOperandVector getOutputTensorOperands();
// [MOVES] Return the types of the subset of output operands that are
// of buffer type.
SmallVector<MemRefType> getOutputBufferTypes();
// [MOVES] Return the types of the subset of output operands
// that are of tensor type.
SmallVector<RankedTensorType> getOutputTensorTypes();
// Input and Output arguments handling.
// [MOVES] Return the range over input and output operands.
OpOperandVector getInputAndOutputOperands();
// [STAYS] Return true if the payload uses the value loaded from
// `opOperand`. This is useful to avoid loading from "write-only"
// memory that may be
// uninitialized, as well as properly cloning "read-write" operands.
bool payloadUsesValueFromOperand(OpOperand *),
// [MOVES] Return true if `opOperand` is an input tensor.
bool isInputTensor(OpOperand *);
// [MOVES] Return true if `opOperand` is an output tensor.
bool isOutputTensor(OpOperand *);
// [STAYS] Return true if `opOperand` is an init tensor.
// This is true when it is an output tensor
// operand whose value is used in the payload region.
bool isInitTensor(OpOperand *);
// [DOES-NOT-HAVE-TO-BE-IN-ANY-INTERFACE] Return
// the `opOperand` rank or zero for scalars.
int64_t getRank(OpOperand*);
// [STAYS] Return the output block arguments of the region.
Block::BlockArgListType getRegionOutputArgs();
// [DOES-NOT-HAVE-TO-BE-IN-ANY-INTERFACE] Return
// the `opOperand` shape or an empty vector for scalars.
ArrayRef<int64_t> getShape(OpOperand*":$opOperand);
// [DOES-NOT-HAVE-TO-BE-IN-ANY-INTERFACE] Return
// true if the `opOperand` is a scalar value.
bool isScalar(OpOperand*),
// [STAYS] Return the block argument for an `opOperand`.
BlockArgument getTiedBlockArgument(OpOperand *);
// [STAYS] Return the operand for a `blockArgument`.
OpOperand* getTiedOpOperand(BlockArgument);
// [STAYS] Return the input or output indexing map for `opOperand`.
AffineMap getTiedIndexingMap(OpOperand*);
// [STAYS] Return the indexing map for a `result`.
AffineMap getTiedIndexingMapForResult(OpResult);
// [MOVES] Return the result tied to `opOperand`.
OpResult getTiedOpResult(OpOperand*);
// [STAYS] Return the value yielded by the region corresponding
// to an output `opOperand`.
OpOperand * getTiedYieldValue(OpOperand*);
The most important methods here are hasBufferSemantics
and hasTensorSemantics
that also follow from how the dst-style ops are bufferized.
// Other interface methods.
// [STAYS] Return the single block constituting the body of the
// operation by calling the getBody method on the concrete
// operation.
Block* getBlock();
// [STAYS] Return the iterator types attribute.
ArrayAttr iterator_types();
// [STAYS] Return true if the indexing map is depending on
// the current op instance. This means that the indexing map
// is dynamically synthesized by using the op instance's concrete
// attributes, instead of being static for all
// instances of the same op kind.
bool hasDynamicIndexingMaps();
// [STAYS] Verify all attributes used by indexing maps are valid.
LogicalResult verifyIndexingMapRequiredAttributes();
// [STAYS] Return the indexing maps attribute.
ArrayAttr getIndexingMaps();
// [STAYS] Return the indexing maps within the current operation.
SmallVector<AffineMap> getIndexingMapsArray();
// [STAYS] Return true if any of the operands has a dynamic shape.
bool hasDynamicShape();
// [MOVES] Return whether the op has only MemRef input and outputs.
bool hasBufferSemantics();
// [MOVES] Return whether the op has only RankedTensor input and outputs.
bool hasTensorSemantics();
// [STAYS] Return the name registered for this op when lowering to an
// external library call.
std::string getLibraryCallName();
// [STAYS] Return whether the op accesses the iteration indices.
bool hasIndexSemantics();
// [STAYS] Linalg generalization hooks.
AffineMap getLoopsToShapesMap();
AffineMap getShapesToLoopsMap();
bool canOpOperandsBeDropped(ArrayRef<OpOperand *>);
std::pair<int64_t, int64_t> getResultsPositionInLoopsToShapeMap();
SmallVector<int64_t> getStaticShape();
SmallVector<int64_t, 4> getStaticLoopRanges();
// Other interface methods.
// [MOVES] Clone the current operation with the given location
// and operands. This is used to abstract away the optional
// underlying region creation. This
// does not change the balance between input, output_buffer and
// init_tensors operands.
Operation* clone(OpBuilder &, Location, TypeRange, ValueRange),
// [NOT USED ANYWHERE:CAN IT BE REMOVED?] Clone the current
// operation with the given location, operands
// and BlockAndValueMapping. This is used to abstract away the
// optional underlying region creation. This does not change the
// balance between input, output_buffer and init_tensors operands.
Operation * cloneWithMapper(OpBuilder &, "Location, TypeRange,
ValueRange, BlockAndValueMapping &),
// [MOVES] Clone the current operation with the given location,
// operands and BlockAndValueMapping but leave the regions
// empty. This is used to abstract away the optional underlying
// region creation. This does not change the balance between
// input, output_buffer and init_tensors operands.
Operation* cloneWithoutRegions(OpBuilder &, Location,
TypeRange, ValueRange);
// [STAYS] Returns the region builder for constructing the body for
// linalg.generic.
// Returns a null function if this named op does not define a region
// builder.
std::function<void(ImplicitLocOpBuilder &, Block &,
ArrayRef<NamedAttribute>)> getRegionBuilder();
// [STAYS] Return true if all the indexing maps are projected permutations.
// Otherwise return false.
bool hasOnlyProjectedPermutations();
// [STAYS]
let extraClassDeclaration = [{
SmallVector<Value, 4> createFlatListOfOperandDims(OpBuilder &, Location);
SmallVector<int64_t, 4> createFlatListOfOperandStaticDims();
SmallVector<Range, 4> createLoopRanges(OpBuilder &b, Location loc);
SmallVector<int64_t, 4> computeStaticLoopSizes();
LogicalResult reifyResultShapes(OpBuilder &b,
ReifiedRankedShapedTypeDims &reifiedReturnShapes);
ArrayAttr getIteratorTypes() { return iterator_types(); }
void setNumInputs(unsigned num) { setOperandSegmentAt(0, num); }
void setNumOutputBuffers(unsigned num) { setOperandSegmentAt(1, num); }
}]