1107 lines
39 KiB
TableGen
1107 lines
39 KiB
TableGen
//===- ShapeOps.td - Shape operations definition -----------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This is the operation definition file for Shape dialect operations.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SHAPE_OPS
|
|
#define SHAPE_OPS
|
|
|
|
include "mlir/Dialect/Shape/IR/ShapeBase.td"
|
|
include "mlir/Interfaces/CallInterfaces.td"
|
|
include "mlir/Interfaces/CastInterfaces.td"
|
|
include "mlir/Interfaces/ControlFlowInterfaces.td"
|
|
include "mlir/Interfaces/InferTypeOpInterface.td"
|
|
include "mlir/Interfaces/SideEffectInterfaces.td"
|
|
include "mlir/IR/OpAsmInterface.td"
|
|
include "mlir/Interfaces/FunctionInterfaces.td"
|
|
include "mlir/IR/SymbolInterfaces.td"
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Shape op definitions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Base class for the operation in this dialect
|
|
class Shape_Op<string mnemonic, list<Trait> traits = []> :
|
|
Op<ShapeDialect, mnemonic, traits>;
|
|
|
|
def Shape_AddOp : Shape_Op<"add",
|
|
[Commutative, Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Addition of sizes and indices";
|
|
let description = [{
|
|
Adds two sizes or indices. If either operand is an error it will be
|
|
propagated to the result. The operands can be of type `size` or `index`. If
|
|
at least one of the operands can hold an error, i.e. if it is of type
|
|
`size`, the result must be of type `size`. If error propagation is not
|
|
possible because both operands are of type `index` then the result may be
|
|
of type `size` or `index`.
|
|
}];
|
|
|
|
let arguments = (ins Shape_SizeOrIndexType:$lhs, Shape_SizeOrIndexType:$rhs);
|
|
let results = (outs Shape_SizeOrIndexType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_BroadcastOp : Shape_Op<"broadcast", [Commutative, Pure]> {
|
|
let summary = "Returns the broadcasted output shape of two or more inputs";
|
|
let description = [{
|
|
Returns the broadcasted shape for input shapes or extent tensors. The rest
|
|
of this description is simplified for the 2 input case but can be extended
|
|
to more inputs. Both operands can be of type `shape.shape` or
|
|
`tensor<?xindex>`. The result is of type `shape.shape` and, if both
|
|
operands are tensors, may be of type `tensor<?xindex>`.
|
|
|
|
If the two operand shapes are of different rank the smaller one is padded
|
|
with 1's from the left. The resulting broadcasted shape is then defined as
|
|
|
|
result[i] = lhs[i] if lhs[i] == rhs[i]
|
|
= lhs[i] if rhs[i] == 1
|
|
= rhs[i] if lhs[i] == 1.
|
|
|
|
In case the resulting shape is undefined, i.e. if corresponding extents are
|
|
different from each other but none is 1, the result is an error shape.
|
|
Likewise error values are propagated if any of the operands holds an error
|
|
value. If the result type is an extent tensor (and can therefore not hold
|
|
the error value) the behavior may be undefined. The optional string
|
|
attribute can be used to describe the error case.
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$shapes,
|
|
OptionalAttr<StrAttr>:$error);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let builders = [OpBuilder<(ins "Value":$shape)>];
|
|
|
|
let assemblyFormat = [{
|
|
$shapes attr-dict `:` type($shapes) `->` type($result)
|
|
}];
|
|
|
|
let builders = [OpBuilder<(ins "::mlir::Type":$result,
|
|
"::mlir::Value":$lhs, "::mlir::Value":$rhs,
|
|
"/*optional*/ ::mlir::StringAttr":$error), [{
|
|
build($_builder, $_state, result, ::llvm::ArrayRef({lhs, rhs}),
|
|
error);
|
|
}]>
|
|
];
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ConstShapeOp : Shape_Op<"const_shape",
|
|
[ConstantLike, Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Creates a constant shape or extent tensor";
|
|
let description = [{
|
|
Creates a constant shape or extent tensor. The individual extents are given
|
|
as the `shape` attribute. The number of these values equals the shape's
|
|
rank.
|
|
|
|
```mlir
|
|
%0 = shape.const_shape [] : !shape.shape
|
|
%1 = shape.const_shape [1, 2, 3] : !shape.shape
|
|
%2 = shape.const_shape [4, 5, 6] : tensor<3xindex>
|
|
```
|
|
}];
|
|
let arguments = (ins IndexElementsAttr:$shape);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let hasCustomAssemblyFormat = 1;
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
}
|
|
|
|
def Shape_ConstSizeOp : Shape_Op<"const_size", [
|
|
ConstantLike,
|
|
Pure,
|
|
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>
|
|
]> {
|
|
let summary = "Creates a constant of type `shape.size`";
|
|
let description = [{
|
|
Creates a `shape.size` type representing the constant size given by `value`.
|
|
|
|
```mlir
|
|
%x = shape.const_size 10
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins IndexAttr:$value);
|
|
let results = (outs Shape_SizeType:$result);
|
|
|
|
let builders = [OpBuilder<(ins "int64_t":$value)>];
|
|
|
|
let assemblyFormat = "$value attr-dict";
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_DivOp : Shape_Op<"div", [Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Division of sizes and indices";
|
|
let description = [{
|
|
Divides two sizes or indices. If either operand is an error it will be
|
|
propagated to the result. The operands can be of type `size` or `index`.
|
|
If at least one of the operands can hold an error, i.e. if it is of type
|
|
`size`, the result must be of type `size`. If error propagation is not
|
|
possible because both operands are of type `index` then the result may be
|
|
of type `size` or `index`. If both operands and result are of type
|
|
`index`, their runtime values could be negative. The result is rounded
|
|
toward negative infinity, i.e. floor(lhs / rhs), such that
|
|
|
|
div(lhs, rhs) * rhs + mod(lhs, rhs) = lhs
|
|
|
|
always holds. If any of the values is of type `size`, the behavior for
|
|
negative value is undefined.
|
|
}];
|
|
|
|
let arguments = (ins Shape_SizeOrIndexType:$lhs,
|
|
Shape_SizeOrIndexType:$rhs);
|
|
let results = (outs Shape_SizeOrIndexType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ShapeEqOp : Shape_Op<"shape_eq", [Pure, Commutative]> {
|
|
let summary = "Returns whether the input shapes or extent tensors are equal";
|
|
let description = [{
|
|
Takes one or more shape or extent tensor operands and determines whether
|
|
they are equal. When extent tensors are compared to shapes they are
|
|
regarded as their equivalent non-error shapes. Error shapes can be tested
|
|
for equality like any other shape value, meaning that the error value is
|
|
equal to itself.
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$shapes);
|
|
let results = (outs I1:$result);
|
|
|
|
// Convenience builder alias for the binary version.
|
|
let builders = [
|
|
OpBuilder<(ins "::mlir::Value":$lhs, "::mlir::Value":$rhs),
|
|
[{ build($_builder, $_state, ::llvm::ArrayRef({lhs, rhs})); }]>,
|
|
];
|
|
|
|
let assemblyFormat = "$shapes attr-dict `:` type($shapes)";
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_FromExtentsOp : Shape_Op<"from_extents", [Pure]> {
|
|
let summary = "Creates a shape from extents";
|
|
let description = [{
|
|
Creates a shape from multiple SSA values representing the extents of
|
|
the shape.
|
|
|
|
```mlir
|
|
// Rank 2 shape.
|
|
%s0 = shape.from_extents %a, %b
|
|
// Rank 0 shape.
|
|
%s1 = shape.from_extents
|
|
```
|
|
}];
|
|
let arguments = (ins Variadic<Shape_SizeOrIndexType>:$extents);
|
|
let results = (outs Shape_ShapeType:$shape);
|
|
|
|
let assemblyFormat = "$extents attr-dict `:` type($extents)";
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_FromExtentTensorOp : Shape_Op<"from_extent_tensor", [Pure]> {
|
|
let summary = "Creates a shape from a tensor of extents";
|
|
let description = [{
|
|
Creates a shape from a 1D integral tensor of extents. The rank of the
|
|
resulting shape equals the number of elements in the tensor, and the
|
|
extents match the values of the elements.
|
|
}];
|
|
|
|
let arguments = (ins 1DTensorOf<[Index]>:$input);
|
|
let results = (outs Shape_ShapeType:$result);
|
|
|
|
let assemblyFormat = "$input attr-dict `:` type($input)";
|
|
}
|
|
|
|
def Shape_IsBroadcastableOp : Shape_Op<"is_broadcastable", [Commutative]> {
|
|
let summary = "Determines if 2+ shapes can be successfully broadcasted";
|
|
let description = [{
|
|
Given multiple input shapes or extent tensors, return a predicate
|
|
specifying if they are broadcastable. This broadcastable follows the same
|
|
logic as what shape.broadcast documents.
|
|
|
|
Concretely, shape.is_broadcastable returning true implies that
|
|
shape.broadcast will not give an error, and shape.cstr_broadcastable will
|
|
not result in an assertion failure. Similarly, false implies an error or
|
|
assertion failure.
|
|
|
|
Example:
|
|
```mlir
|
|
%true = shape.is_broadcastable [2,2], [3,1,2]
|
|
%false = shape.is_broadcastable [2,2], [3,2]
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$shapes);
|
|
let results = (outs I1:$result);
|
|
|
|
let builders = [
|
|
OpBuilder<(ins "::mlir::Value":$lhs, "::mlir::Value":$rhs),
|
|
[{ build($_builder, $_state, ::llvm::ArrayRef({lhs, rhs})); }]>,
|
|
];
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
|
|
let assemblyFormat = "$shapes attr-dict `:` type($shapes)";
|
|
}
|
|
|
|
def Shape_RankOp : Shape_Op<"rank",
|
|
[Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Gets the rank of a shape";
|
|
let description = [{
|
|
Returns the rank of the shape or extent tensor, i.e. the number of extents.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$shape);
|
|
let results = (outs Shape_SizeOrIndexType:$rank);
|
|
|
|
let assemblyFormat = "$shape attr-dict `:` type($shape) `->` type($rank)";
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ToExtentTensorOp : Shape_Op<"to_extent_tensor", [
|
|
DeclareOpInterfaceMethods<CastOpInterface>, Pure
|
|
]> {
|
|
let summary = "Creates a dimension tensor from a shape";
|
|
let description = [{
|
|
Converts a shape to a 1D integral tensor of extents. The number of elements
|
|
in the tensor equals the rank of the shape, and the elements equal the
|
|
extents of the shape.
|
|
|
|
If the shape represents an error, this op's behavior is undefined.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$input);
|
|
let results = (outs IndexTensor:$result);
|
|
|
|
let assemblyFormat = "$input attr-dict `:` type($input) `->` type($result)";
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_DimOp : Shape_Op<"dim",
|
|
[Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Gets the specified extent from the shape of a shaped input";
|
|
let description = [{
|
|
Gets the extent indexed by `dim` from the shape of the `value` operand. If
|
|
the index is error or out-of-bound then it returns an invalid size if the
|
|
return type carries error information else the behavior is undefined.
|
|
|
|
This is a convenience op that performs the equivalent of getting the extent
|
|
of a shape (e.g., `dim(x, i) == get_extent(shape_of(x), i)`).
|
|
}];
|
|
let arguments = (ins AnyShaped:$value,
|
|
Shape_SizeOrIndexType:$index);
|
|
let results = (outs Shape_SizeOrIndexType:$extent);
|
|
let assemblyFormat = "$value `,` $index attr-dict `:` type($value) `,`"
|
|
"type($index) `->` type($extent)";
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Get the `index` value as integer if it is constant.
|
|
std::optional<int64_t> getConstantIndex();
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_GetExtentOp : Shape_Op<"get_extent",
|
|
[Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Gets the specified extent from a shape or extent tensor";
|
|
let description = [{
|
|
Gets the extent indexed by `dim` from the `shape` operand. If the shape is
|
|
an error then it returns an invalid size.
|
|
}];
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$shape,
|
|
Shape_SizeOrIndexType:$dim);
|
|
let results = (outs Shape_SizeOrIndexType:$extent);
|
|
let assemblyFormat = "$shape `,` $dim attr-dict `:` type($shape) `,` "
|
|
"type($dim) `->` type($extent)";
|
|
|
|
let builders = [
|
|
// Builder that allows passing a constant dimension as a simple integer.
|
|
OpBuilder<(ins "Value":$shape, "int64_t":$dim)>
|
|
];
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Get the `dim` value as integer if it is constant.
|
|
std::optional<int64_t> getConstantDim();
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_IndexToSizeOp : Shape_Op<"index_to_size", [Pure]> {
|
|
let summary = "Converts a standard index to a shape size";
|
|
let description = [{
|
|
Converts a standard index to a `shape.size`. This operation and its
|
|
inverse, `size_to_index`, facilitate index conversion between the standard
|
|
and the shape dialect.
|
|
|
|
The behavior is undefined for negative indices.
|
|
}];
|
|
|
|
let arguments = (ins Index:$arg);
|
|
let results = (outs Shape_SizeType:$result);
|
|
|
|
let assemblyFormat = "$arg attr-dict";
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
}
|
|
|
|
def Shape_MaxOp : Shape_Op<"max",
|
|
[Commutative, Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Elementwise maximum";
|
|
let description = [{
|
|
Computes the elementwise maximum of two sizes or shapes with equal ranks.
|
|
If either operand is an error, then an error will be propagated to the
|
|
result. If the input types mismatch or the ranks do not match, then the
|
|
result is an error.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrSizeType:$lhs, Shape_ShapeOrSizeType:$rhs);
|
|
let results = (outs Shape_ShapeOrSizeType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_MeetOp : Shape_Op<"meet",
|
|
[Commutative, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Returns the least general shape or size of its operands";
|
|
let description = [{
|
|
An operation that computes the least general shape or dim of input operands.
|
|
This effectively asserts that corresponding static dimensions are equal.
|
|
The behavior is to match each element of the shape/size and propagate the
|
|
most restrictive information, returning an invalid shape if there are
|
|
contradictory requirements. E.g., using pseudo code
|
|
|
|
```
|
|
shape.meet([*], [*]) -> [*]
|
|
shape.meet([*], [1, ?]) -> [1, ?]
|
|
shape.meet([1, 2], [1, ?]) -> [1, 2]
|
|
shape.meet([*], [1, 2]) -> [1, 2]
|
|
shape.meet([], []) -> []
|
|
shape.meet([], [*]) -> []
|
|
shape.meet([], [?, ?]) -> [invalid]
|
|
shape.meet([1, ?], [2, ?, ?]) -> [invalid]
|
|
```
|
|
|
|
`shape.meet` also allows specifying an optional error string, that may be
|
|
used to return an error to the user upon mismatch of dimensions.
|
|
|
|
```mlir
|
|
%c = shape.meet %a, %b, error="<reason>" : !shape.shape, !shape.shape -> !shape.shape
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins
|
|
Shape_AnyShapeOrSizeType:$arg0,
|
|
Shape_AnyShapeOrSizeType:$arg1,
|
|
OptionalAttr<StrAttr>:$error);
|
|
let results = (outs Shape_AnyShapeOrSizeType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$arg0 `,` $arg1 (`,` `error` `=` $error^)? attr-dict `:`
|
|
type($arg0) `,` type($arg1) `->` type($result)
|
|
}];
|
|
}
|
|
|
|
def Shape_MinOp : Shape_Op<"min",
|
|
[Commutative, Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Elementwise minimum";
|
|
let description = [{
|
|
Computes the elementwise minimum of two sizes or shapes with equal ranks.
|
|
If either operand is an error, then an error will be propagated to the
|
|
result. If the input types mismatch or the ranks do not match, then the
|
|
result is an error.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrSizeType:$lhs, Shape_ShapeOrSizeType:$rhs);
|
|
let results = (outs Shape_ShapeOrSizeType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_MulOp : Shape_Op<"mul",
|
|
[Commutative, Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Multiplication of sizes and indices";
|
|
let description = [{
|
|
Multiplies two sizes or indices. If either operand is an error it will be
|
|
propagated to the result. The operands can be of type `size` or `index`. If
|
|
at least one of the operands can hold an error, i.e. if it is of type
|
|
`size`, the result must be of type `size`. If error propagation is not
|
|
possible because both operands are of type `index` then the result may be
|
|
of type `size` or `index`.
|
|
}];
|
|
|
|
let arguments = (ins Shape_SizeOrIndexType:$lhs, Shape_SizeOrIndexType:$rhs);
|
|
let results = (outs Shape_SizeOrIndexType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_NumElementsOp : Shape_Op<"num_elements",
|
|
[Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Returns the number of elements for a given shape";
|
|
let description = [{
|
|
Returns the number of elements for a given shape which is the product of
|
|
its extents. If the argument is of type `shape` then the result will be of
|
|
type `size` and potential errors will be propagated. Otherwise, if the
|
|
argument is and extent tensor `tensor<?xindex>` then the result will be of
|
|
type `index`.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$shape);
|
|
let results = (outs Shape_SizeOrIndexType:$result);
|
|
|
|
let assemblyFormat = "$shape attr-dict `:` type($shape) `->` type($result)";
|
|
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ReduceOp : Shape_Op<"reduce",
|
|
[SingleBlockImplicitTerminator<"YieldOp">]> {
|
|
let summary = "Returns an expression reduced over a shape or extent tensor";
|
|
let description = [{
|
|
An operation that takes as input a shape or extent tensor, and a number of
|
|
initial values. This operation has a region that is applied repeatedly for
|
|
every extent of the input. Starting with the initial values, the individual
|
|
extents are then aggregated as defined by the associated region.
|
|
|
|
Conceptually this op performs the following reduction:
|
|
|
|
```
|
|
res[] = init;
|
|
for (int i = 0, i < shape.rank(); i++) {
|
|
res = reduce(i, shape[i], res[0], ..., res[n]);
|
|
}
|
|
```
|
|
|
|
Where `reduce` represents the region attached and the result of the reduce
|
|
op is the last computed output of the reduce region. As an example, the
|
|
number of elements can be computed as follows:
|
|
|
|
```mlir
|
|
func.func @reduce(%shape : !shape.shape, %init : !shape.size) ->
|
|
!shape.size {
|
|
%num_elements = shape.reduce(%shape, %init) -> !shape.size {
|
|
^bb0(%index: index, %dim: !shape.size, %acc: !shape.size):
|
|
%updated_acc = "shape.mul"(%acc, %dim) :
|
|
(!shape.size, !shape.size) -> !shape.size
|
|
shape.yield %updated_acc : !shape.size
|
|
}
|
|
return %num_elements : !shape.size
|
|
}
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$shape,
|
|
Variadic<AnyType>:$initVals);
|
|
let results = (outs Variadic<AnyType>:$result);
|
|
let regions = (region SizedRegion<1>:$region);
|
|
|
|
let builders = [OpBuilder<(ins "Value":$shape, "ValueRange":$initVals)>];
|
|
|
|
let hasCustomAssemblyFormat = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ShapeOfOp : Shape_Op<"shape_of",
|
|
[Pure, InferTypeOpAdaptorWithIsCompatible]> {
|
|
let summary = "Returns shape of a value or shaped type operand";
|
|
|
|
let description = [{
|
|
The operation takes a value or a shaped operand as an argument and it
|
|
returns a shape or extent tensor.
|
|
}];
|
|
|
|
let arguments = (ins AnyTypeOf<[AnyShaped, Shape_ValueShapeType]>:$arg);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let assemblyFormat = "$arg attr-dict `:` type($arg) `->` type($result)";
|
|
|
|
let hasCanonicalizer = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_ValueOfOp : Shape_Op<"value_of", [Pure]> {
|
|
let summary = "Returns value of a !shape.value_shape operand";
|
|
|
|
let description = [{
|
|
The operation takes !shape.value_shape, a.k.a. (value, shape) tuple as an
|
|
argument, and returns its value. The behavior is undefined for unknown and
|
|
invalid arguments.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ValueShapeType:$arg);
|
|
let results = (outs AnyShaped:$result);
|
|
|
|
let assemblyFormat = "$arg attr-dict `:` type($result)";
|
|
}
|
|
|
|
def Shape_SizeToIndexOp : Shape_Op<"size_to_index", [
|
|
DeclareOpInterfaceMethods<CastOpInterface>, Pure
|
|
]> {
|
|
let summary = "Casts between index types of the shape and standard dialect";
|
|
let description = [{
|
|
Converts a `shape.size` to a standard index. This operation and its
|
|
inverse, `index_to_size`, facilitate index conversion between the standard
|
|
and the shape dialect. The behavior is undefined for unknown and invalid
|
|
arguments.
|
|
}];
|
|
|
|
let arguments = (ins Shape_SizeOrIndexType:$arg);
|
|
let results = (outs Index:$result);
|
|
|
|
let assemblyFormat = "$arg attr-dict `:` type($arg)";
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
}
|
|
|
|
def Shape_ValueAsShapeOp : Shape_Op<"value_as_shape", [Pure]> {
|
|
let summary = "Returns value as a shape";
|
|
|
|
let description = [{
|
|
The operations takes a ValueShape and returns a Shape corresponding to the
|
|
value. If the input value cannot be shape (e.g., not a 1D tensor of
|
|
integral value representing sizes) then this propagages the error shape.
|
|
E.g.,
|
|
|
|
```mlir
|
|
// The following
|
|
%0 = arith.constant dense<[1,2]> : tensor<2xi32>
|
|
%shape = shape.value_as_shape %0 : tensor<2xi32> -> !shape.shape
|
|
// is equivalent to
|
|
%shape' = shape.const_shape [1, 2] : !shape.shape
|
|
```
|
|
|
|
This operation is the complement of `shape_of` wrt ValueShape values.
|
|
}];
|
|
|
|
let arguments = (ins AnyTypeOf<[1DTensorOf<[AnyInteger, Index]>,
|
|
Shape_ValueShapeType]>:$arg);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let assemblyFormat = "$arg attr-dict `:` type($arg) `->` type($result)";
|
|
}
|
|
|
|
def Shape_WithOp : Shape_Op<"with_shape", [Pure]> {
|
|
let summary = "Returns ValueShape with given shape";
|
|
let description = [{
|
|
Returns ValueShape with the shape updated to match the shape operand. That
|
|
is a new ValueShape tuple is created with value equal to `operand`'s
|
|
value and shape equal to `shape`. If the ValueShape and given `shape` are
|
|
non-conformant, then the returned ValueShape will represent an error of
|
|
this mismatch. Similarly if either inputs are in an error state, then an
|
|
error is propagated.
|
|
|
|
Usage:
|
|
%0 = shape.with_shape %1, %2 : tensor<...>, !shape.shape
|
|
|
|
This is used, for example, where one combines shape function calculations
|
|
and/or call one shape function from another. E.g.,
|
|
|
|
```mlir
|
|
func.func @shape_foobah(%a: !shape.value_shape,
|
|
%b: !shape.value_shape,
|
|
%c: !shape.value_shape) -> !shape.shape {
|
|
%0 = call @shape_foo(%a, %b) :
|
|
(!shape.value_shape, !shape.value_shape) -> !shape.shape
|
|
%1 = shape.with_shape %b, %0 : !shape.value_shape, !shape.shape
|
|
%2 = call @shape_bah(%c, %1) :
|
|
(!shape.value_shape, !shape.value_shape) -> !shape.shape
|
|
return %2 : !shape.shape
|
|
}
|
|
```
|
|
|
|
This op need not be a refinement of the shape. In non-error cases the input
|
|
ValueShape's value and shape are conformant and so too for the output, but
|
|
the result may be less specified than `operand`'s shape as `shape` is
|
|
merely used to construct the new ValueShape. If join behavior is desired
|
|
then a join op should be used.
|
|
}];
|
|
|
|
let arguments = (ins AnyTypeOf<[AnyShaped, Shape_ValueShapeType]>:$operand,
|
|
Shape_ShapeOrExtentTensorType:$shape);
|
|
let results = (outs Shape_ValueShapeType:$result);
|
|
|
|
let assemblyFormat = "operands attr-dict `:` type($operand) `,` type($shape)";
|
|
}
|
|
|
|
def Shape_YieldOp : Shape_Op<"yield",
|
|
[HasParent<"ReduceOp, FunctionLibraryOp">,
|
|
Pure,
|
|
ReturnLike,
|
|
Terminator]> {
|
|
let summary = "Returns the value to parent op";
|
|
|
|
let arguments = (ins Variadic<AnyType>:$operands);
|
|
|
|
let builders = [OpBuilder<(ins), [{ build($_builder, $_state, {}); }]>];
|
|
|
|
let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
// TODO: Add Ops: if_static, if_ranked
|
|
|
|
// For testing usage.
|
|
def Shape_DebugPrintOp : Shape_Op<"debug_print", []> {
|
|
let summary = "Prints the input shape or size";
|
|
let description = [{
|
|
Prints the input dim or shape and passes through input.
|
|
|
|
Note: This is intended for testing and debugging only.
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrSizeType:$input);
|
|
let results = (outs Shape_ShapeOrSizeType:$output);
|
|
}
|
|
|
|
def Shape_SplitAtOp : Shape_Op<"split_at", [Pure]> {
|
|
let summary = "Splits a shape at a given index";
|
|
let description = [{
|
|
Splits a shape at a given dimension `index`, returning two shapes. If
|
|
`index` is negative, it is treated as indexing from the back of the shape.
|
|
This negative-handling behavior is important when handling unranked shapes,
|
|
where the positive index is not necessarily knowable due to a dynamic
|
|
number of leading dimensions. If the result is in extent tensor form out of
|
|
bounds indices result in undefined behavior.
|
|
|
|
Examples:
|
|
- split_at([4,5,6], index=0) -> [], [4,5,6]
|
|
- split_at([4,5,6], index=1) -> [4], [5,6]
|
|
- split_at([4,5,6], index=2) -> [4,5], [6]
|
|
- split_at([4,5,6], index=3) -> [4,5,6], []
|
|
- split_at([4,5,6], index=4) -> error
|
|
- split_at([4,5,6], index=-1) -> [4,5], [6]
|
|
- split_at([4,5,6], index=-2) -> [4], [5,6]
|
|
- split_at([4,5,6], index=-3) -> [], [4,5,6]
|
|
- split_at([4,5,6], index=-4) -> error
|
|
|
|
Requires:
|
|
- `index` is in the range [-rank(operand),rank(operand)]
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$operand,
|
|
Shape_SizeOrIndexType:$index);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$head,
|
|
Shape_ShapeOrExtentTensorType:$tail);
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_ConcatOp : Shape_Op<"concat", [Pure]> {
|
|
let summary = "Concatenates two shapes";
|
|
let description = [{
|
|
Creates a shape whose dimensions consist of first the dimensions from `lhs`
|
|
followed by the dimensions of `rhs`.
|
|
|
|
Example:
|
|
concat([2,3], [4,5]) -> [2,3,4,5]
|
|
concat([], []) -> []
|
|
concat([], [4,5,6]) -> [4,5,6]
|
|
}];
|
|
|
|
let arguments = (ins Shape_ShapeOrExtentTensorType:$lhs,
|
|
Shape_ShapeOrExtentTensorType:$rhs);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let assemblyFormat = [{
|
|
$lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) `->` type($result)
|
|
}];
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Shape constraint related ops.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// TODO: Move the code below and witnesses to a different file.
|
|
def Shape_AnyOp : Shape_Op<"any", [Commutative,
|
|
Pure]> {
|
|
let summary = "Return any combination of the input shapes";
|
|
let description = [{
|
|
This operation takes multiple input shapes or extent tensors and returns
|
|
some combination of their dimensions. This can be best seen with examples
|
|
below.
|
|
|
|
The result is undefined, but still side-effect free, in cases where the
|
|
inputs have differing ranks or differ in extents of shared dimensions.
|
|
|
|
Example:
|
|
```mlir
|
|
%s0 = shape.any [2,?], [?,3] // [2,3]
|
|
%s1 = shape.any [?,?], [1,2] // [1,2]
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$inputs);
|
|
let results = (outs Shape_ShapeOrExtentTensorType:$result);
|
|
|
|
let assemblyFormat = "$inputs attr-dict `:` type($inputs) `->` type($result)";
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_AssumingAllOp : Shape_Op<"assuming_all", [Commutative, Pure]> {
|
|
let summary = "Return a logical AND of all witnesses";
|
|
let description = [{
|
|
Used to simplify constraints as any single failing precondition is enough
|
|
to prevent execution.
|
|
|
|
"assuming" operations represent an execution order restriction to the
|
|
compiler, information for dependent code to rely on (by assuming), and
|
|
nothing else. They should not exist after a program is fully lowered and
|
|
ready to execute.
|
|
|
|
Example:
|
|
```mlir
|
|
%w0 = shape.cstr_broadcastable [2,2], [3,1,2] // Passing
|
|
%w1 = shape.cstr_broadcastable [2,2], [3,2] // Failure
|
|
%w2 = shape.cstr_eq [1,2], [1,2], [1,2] // Passing
|
|
%wf = shape.assuming_all %w0, %w1 // Failure
|
|
%wt = shape.assuming_all %w0, %w2 // Passing
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_WitnessType>:$inputs);
|
|
let results = (outs Shape_WitnessType:$result);
|
|
|
|
let assemblyFormat = "$inputs attr-dict";
|
|
|
|
let hasFolder = 1;
|
|
let hasCanonicalizer = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_AssumingOp : Shape_Op<"assuming", [
|
|
SingleBlockImplicitTerminator<"AssumingYieldOp">,
|
|
DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>,
|
|
RecursiveMemoryEffects]> {
|
|
let summary = "Execute the region";
|
|
let description = [{
|
|
Executes the region assuming all witnesses are true.
|
|
|
|
"assuming" operations represent an execution order restriction to the
|
|
compiler, information for dependent code to rely on (by assuming), and
|
|
nothing else. They should not exist after a program is fully lowered and
|
|
ready to execute.
|
|
}];
|
|
let arguments = (ins Shape_WitnessType:$witness);
|
|
let regions = (region SizedRegion<1>:$doRegion);
|
|
let results = (outs Variadic<AnyType>:$results);
|
|
|
|
let extraClassDeclaration = [{
|
|
// Inline the region into the region containing the AssumingOp and delete
|
|
// the AssumingOp.
|
|
//
|
|
// This does no checks on the inputs to the AssumingOp.
|
|
static void inlineRegionIntoParent(AssumingOp &op,
|
|
PatternRewriter &rewriter);
|
|
}];
|
|
|
|
let builders = [
|
|
OpBuilder<(ins "Value":$witness,
|
|
CArg<"function_ref<SmallVector<Value, 2>(OpBuilder &, Location)>">)>
|
|
];
|
|
|
|
let hasCanonicalizer = 1;
|
|
let hasCustomAssemblyFormat = 1;
|
|
}
|
|
|
|
def Shape_AssumingYieldOp : Shape_Op<"assuming_yield",
|
|
[Pure, ReturnLike, Terminator, HasParent<"AssumingOp">]> {
|
|
let summary = "Yield operation";
|
|
let description = [{
|
|
This yield operation represents a return operation within the
|
|
`shape.assuming` operation region. The operation takes variable number of
|
|
operands and produces no results. The operand number and types must match
|
|
the number and types of parent `shape.assuming` results.
|
|
}];
|
|
|
|
let arguments = (ins Variadic<AnyType>:$operands);
|
|
|
|
let builders = [
|
|
OpBuilder<(ins), [{ /* nothing to do */ }]>,
|
|
];
|
|
|
|
let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
|
|
}
|
|
|
|
def Shape_CstrBroadcastableOp : Shape_Op<"cstr_broadcastable", [Commutative]> {
|
|
let summary = "Determines if 2+ shapes can be successfully broadcasted";
|
|
let description = [{
|
|
Given input shapes or extent tensors, return a witness specifying if they
|
|
are broadcastable. This broadcastable follows the same logic as what
|
|
shape.broadcast documents.
|
|
|
|
"cstr" operations represent runtime assertions.
|
|
|
|
Example:
|
|
```mlir
|
|
%w0 = shape.cstr_broadcastable [2,2], [3,1,2] // Passing
|
|
%w1 = shape.cstr_broadcastable [2,2], [3,2] // Failure
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$shapes);
|
|
let results = (outs Shape_WitnessType:$result);
|
|
|
|
let assemblyFormat = "$shapes attr-dict `:` type($shapes)";
|
|
|
|
let builders = [
|
|
OpBuilder<(ins "::mlir::Value":$lhs, "::mlir::Value":$rhs),
|
|
[{ build($_builder, $_state, ::llvm::ArrayRef({lhs, rhs})); }]>,
|
|
];
|
|
|
|
let hasCanonicalizer = 1;
|
|
let hasFolder = 1;
|
|
let hasVerifier = 1;
|
|
}
|
|
|
|
def Shape_CstrEqOp : Shape_Op<"cstr_eq", [Commutative]> {
|
|
let summary = "Determines if all input shapes are equal";
|
|
let description = [{
|
|
Given 1 or more input shapes, determine if all shapes are the exact same.
|
|
|
|
"cstr" operations represent runtime assertions.
|
|
|
|
Example:
|
|
```mlir
|
|
%w0 = shape.cstr_eq [1,2], [1,2], [1,2] // Passing
|
|
%w1 = shape.cstr_eq [2,2], [1,2] // Failure
|
|
```
|
|
}];
|
|
let arguments = (ins Variadic<Shape_ShapeOrExtentTensorType>:$shapes);
|
|
let results = (outs Shape_WitnessType:$result);
|
|
|
|
let assemblyFormat = "$shapes attr-dict `:` type($shapes)";
|
|
|
|
let hasCanonicalizer = 1;
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_ConstWitnessOp : Shape_Op<"const_witness", [ConstantLike, Pure]> {
|
|
let summary = "An operation that returns a statically known witness value";
|
|
let description = [{
|
|
This operation represents a statically known witness result. This can be
|
|
often used to canonicalize/fold constraint and assuming code that will always
|
|
pass.
|
|
|
|
```mlir
|
|
%0 = shape.const_shape [1,2,3]
|
|
%1 = shape.const_shape [1,2,3]
|
|
%w0 = shape.cstr_eq(%0, %1) // Can be folded to "const_witness true"
|
|
%w1 = shape.const_witness true
|
|
%w2 = shape.assuming_all(%w0, %w2) // Can be folded to "const_witness true"
|
|
```
|
|
}];
|
|
let arguments = (ins BoolAttr:$passing);
|
|
let results = (outs Shape_WitnessType:$result);
|
|
|
|
let assemblyFormat = "$passing attr-dict";
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
def Shape_CstrRequireOp : Shape_Op<"cstr_require", []> {
|
|
let summary = "Represents a runtime assertion that an i1 is `true`";
|
|
let description = [{
|
|
Represents a runtime assertion that an i1 is true. It returns a
|
|
!shape.witness to order this assertion.
|
|
|
|
For simplicity, prefer using other cstr_* ops if they are available for a
|
|
given constraint.
|
|
|
|
Example:
|
|
```mlir
|
|
%bool = ...
|
|
%w0 = shape.cstr_require %bool, "msg" // Passing if `%bool` is true.
|
|
```
|
|
|
|
Since this op can be used to express many different possible assertions
|
|
(depending on whatever computation calculated `pred`), the `msg`
|
|
should clarify the nature of the assertion for users.
|
|
}];
|
|
let arguments = (ins I1:$pred, StrAttr:$msg);
|
|
let results = (outs Shape_WitnessType:$result);
|
|
|
|
let assemblyFormat = "$pred `,` $msg attr-dict";
|
|
|
|
let hasFolder = 1;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Shape collection ops.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
def Shape_FunctionLibraryOp : Shape_Op<"function_library",
|
|
[AffineScope, IsolatedFromAbove, NoRegionArguments, SymbolTable, Symbol,
|
|
NoTerminator, OpAsmOpInterface, SingleBlock]> {
|
|
let summary = "Represents shape functions and corresponding ops";
|
|
let description = [{
|
|
Represents a list of shape functions and the ops whose shape transfer
|
|
functions they represent.
|
|
|
|
Example:
|
|
|
|
```mlir
|
|
shape.function_library {
|
|
func @same_result_shape(%arg: !shape.value_shape) -> !shape.shape {
|
|
%0 = shape_of %arg : !shape.value_shape -> !shape.shape
|
|
return %0 : !shape.shape
|
|
}
|
|
} mapping {
|
|
std.atan = @same_result_shape
|
|
}
|
|
```
|
|
}];
|
|
|
|
let arguments = (ins SymbolNameAttr:$sym_name,
|
|
OptionalAttr<StrAttr>:$sym_visibility,
|
|
DictionaryAttr:$mapping);
|
|
let regions = (region AnyRegion:$body);
|
|
|
|
let extraClassDeclaration = [{
|
|
/// Returns an associated shape function for an operation if defined.
|
|
FuncOp getShapeFunction(Operation *op);
|
|
|
|
//===------------------------------------------------------------------===//
|
|
// OpAsmOpInterface
|
|
//===------------------------------------------------------------------===//
|
|
|
|
// This will filter the `shape.` prefix in front of operations inside the
|
|
// func body.
|
|
static StringRef getDefaultDialect() { return "shape";}
|
|
}];
|
|
|
|
let builders = [OpBuilder<(ins "StringRef":$name)>];
|
|
let skipDefaultBuilders = 1;
|
|
let hasCustomAssemblyFormat = 1;
|
|
}
|
|
|
|
def Shape_FuncOp : Shape_Op<"func",
|
|
[AffineScope, AutomaticAllocationScope,
|
|
FunctionOpInterface, IsolatedFromAbove, OpAsmOpInterface]> {
|
|
let summary = "Shape function";
|
|
let description = [{
|
|
An operation with a name containing a single `SSACFG` region which
|
|
represents a shape transfer function or helper function for shape transfer
|
|
function.
|
|
}];
|
|
|
|
let arguments = (ins SymbolNameAttr:$sym_name,
|
|
TypeAttrOf<FunctionType>:$function_type,
|
|
OptionalAttr<DictArrayAttr>:$arg_attrs,
|
|
OptionalAttr<DictArrayAttr>:$res_attrs,
|
|
OptionalAttr<StrAttr>:$sym_visibility);
|
|
let regions = (region AnyRegion:$body);
|
|
|
|
let builders = [OpBuilder<(ins
|
|
"StringRef":$name, "FunctionType":$type,
|
|
CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs,
|
|
CArg<"ArrayRef<DictionaryAttr>", "{}">:$argAttrs)
|
|
>];
|
|
|
|
let extraClassDeclaration = [{
|
|
static FuncOp create(Location location, StringRef name, FunctionType type,
|
|
ArrayRef<NamedAttribute> attrs = {});
|
|
static FuncOp create(Location location, StringRef name, FunctionType type,
|
|
Operation::dialect_attr_range attrs);
|
|
static FuncOp create(Location location, StringRef name, FunctionType type,
|
|
ArrayRef<NamedAttribute> attrs,
|
|
ArrayRef<DictionaryAttr> argAttrs);
|
|
//===------------------------------------------------------------------===//
|
|
// FunctionOpInterface Methods
|
|
//===------------------------------------------------------------------===//
|
|
|
|
/// Returns the region on the current operation that is callable. This may
|
|
/// return null in the case of an external callable object, e.g. an external
|
|
/// function.
|
|
::mlir::Region *getCallableRegion() {
|
|
return isExternal() ? nullptr : &getBody();
|
|
}
|
|
|
|
/// Returns the argument types of this function.
|
|
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }
|
|
|
|
/// Returns the result types of this function.
|
|
ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); }
|
|
|
|
//===------------------------------------------------------------------===//
|
|
// OpAsmOpInterface
|
|
//===------------------------------------------------------------------===//
|
|
|
|
// This will filter the `shape.` prefix in front of operations inside the
|
|
// func body.
|
|
static StringRef getDefaultDialect() { return "shape";}
|
|
|
|
//===------------------------------------------------------------------===//
|
|
// SymbolOpInterface Methods
|
|
//===------------------------------------------------------------------===//
|
|
|
|
bool isDeclaration() { return isExternal(); }
|
|
}];
|
|
let hasCustomAssemblyFormat = 1;
|
|
}
|
|
|
|
def Shape_ReturnOp : Shape_Op<"return",
|
|
[Pure, HasParent<"FuncOp">, ReturnLike, Terminator]> {
|
|
let summary = "Shape function return operation";
|
|
let description = [{
|
|
The `shape.return` operation represents a return operation within a
|
|
function. The operation takes variable number of operands and produces no
|
|
results.
|
|
}];
|
|
|
|
let arguments = (ins Variadic<AnyType>:$operands);
|
|
|
|
let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?";
|
|
|
|
// TODO: Tighten verification.
|
|
}
|
|
|
|
#endif // SHAPE_OPS
|