Skip to content

[mlir][Affine] Let affine.[de]linearize_index omit outer bounds #116103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 55 additions & 9 deletions mlir/include/mlir/Dialect/Affine/IR/AffineOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1059,8 +1059,7 @@ def AffineVectorStoreOp : AffineStoreOpBase<"vector_store"> {
// AffineDelinearizeIndexOp
//===----------------------------------------------------------------------===//

def AffineDelinearizeIndexOp : Affine_Op<"delinearize_index",
[Pure, DeclareOpInterfaceMethods<InferTypeOpInterface>]> {
def AffineDelinearizeIndexOp : Affine_Op<"delinearize_index", [Pure]> {
let summary = "delinearize an index";
let description = [{
The `affine.delinearize_index` operation takes a single index value and
Expand All @@ -1082,6 +1081,25 @@ def AffineDelinearizeIndexOp : Affine_Op<"delinearize_index",
%indices_1 = affine.apply #map1()[%linear_index]
%indices_2 = affine.apply #map2()[%linear_index]
```

The basis may either contain `N` or `N-1` elements, where `N` is the number of results.
If there are N basis elements, the first one will not be used during computations,
but may be used during analysis and canonicalization to eliminate terms from
the `affine.delinearize_index` or to enable conclusions about the total size of
`%linear_index`.

If the basis is fully provided, the delinearize_index operation is said to "have
an outer bound". The builders assume that an `affine.delinearize_index` has
an outer bound by default, as this is how the operation was initially defined.

That is, the example above could also have been written
```mlir
%0:3 = affine.delinearize_index %linear_index into (244, 244) : index, index
```

Note that, due to the constraints of affine maps, all the basis elements must
be strictly positive. A dynamic basis element being 0 or negative causes
undefined behavior.
}];

let arguments = (ins Index:$linear_index,
Expand All @@ -1096,17 +1114,27 @@ def AffineDelinearizeIndexOp : Affine_Op<"delinearize_index",
}];

let builders = [
OpBuilder<(ins "Value":$linear_index, "ValueRange":$basis)>,
OpBuilder<(ins "Value":$linear_index, "ArrayRef<OpFoldResult>":$basis)>,
OpBuilder<(ins "Value":$linear_index, "ArrayRef<int64_t>":$basis)>
OpBuilder<(ins "Value":$linear_index, "ValueRange":$dynamic_basis, "ArrayRef<int64_t>":$static_asis, CArg<"bool", "true">:$hasOuterBound)>,
OpBuilder<(ins "Value":$linear_index, "ValueRange":$basis, CArg<"bool", "true">:$hasOuterBound)>,
OpBuilder<(ins "Value":$linear_index, "ArrayRef<OpFoldResult>":$basis, CArg<"bool", "true">:$hasOuterBound)>,
OpBuilder<(ins "Value":$linear_index, "ArrayRef<int64_t>":$basis, CArg<"bool", "true">:$hasOuterBound)>
];

let extraClassDeclaration = [{
/// Return true if the basis includes a bound on the first index input.
bool hasOuterBound() {
return getMultiIndex().size() == getStaticBasis().size();
}

/// Returns a vector with all the static and dynamic basis values.
SmallVector<OpFoldResult> getMixedBasis() {
OpBuilder builder(getContext());
return ::mlir::getMixedValues(getStaticBasis(), getDynamicBasis(), builder);
}

/// Return a vector that contains the basis of the operation, removing
/// the outer bound if one is present.
SmallVector<OpFoldResult> getEffectiveBasis();
}];

let hasVerifier = 1;
Expand All @@ -1124,13 +1152,21 @@ def AffineLinearizeIndexOp : Affine_Op<"linearize_index",
The `affine.linearize_index` operation takes a sequence of index values and a
basis of the same length and linearizes the indices using that basis.

That is, for indices `%idx_1` through `%idx_N` and basis elements `b_1` through `b_N`,
it computes
That is, for indices `%idx_0` to `%idx_{N-1}` and basis elements `b_0`
(or `b_1`) up to `b_{N-1}` it computes

```
sum(i = 1 to N) %idx_i * product(j = i + 1 to N) B_j
sum(i = 0 to N-1) %idx_i * product(j = i + 1 to N-1) B_j
```

The basis may either have `N` or `N-1` elements, where `N` is the number of
inputs to linearize_index. If `N` inputs are provided, the first one is not used
in computation, but may be used during analysis or canonicalization as a bound
on `%idx_0`.

If all `N` basis elements are provided, the linearize_index operation is said to
"have an outer bound".

If the `disjoint` property is present, this is an optimization hint that,
for all `i`, `0 <= %idx_i < B_i` - that is, no index affects any other index,
except that `%idx_0` may be negative to make the index as a whole negative.
Expand All @@ -1140,7 +1176,9 @@ def AffineLinearizeIndexOp : Affine_Op<"linearize_index",
Example:

```mlir
%linear_index = affine.linearize_index [%index_0, %index_1, %index_2] (2, 3, 5) : index
%linear_index = affine.linearize_index [%index_0, %index_1, %index_2] by (2, 3, 5) : index
// Same effect
%linear_index = affine.linearize_index [%index_0, %index_1, %index_2] by (3, 5) : index
```

In the above example, `%linear_index` conceptually holds the following:
Expand Down Expand Up @@ -1171,12 +1209,20 @@ def AffineLinearizeIndexOp : Affine_Op<"linearize_index",
];

let extraClassDeclaration = [{
/// Return true if the basis includes a bound on the first index input.
bool hasOuterBound() {
return getMultiIndex().size() == getStaticBasis().size();
}

/// Return a vector with all the static and dynamic basis values.
SmallVector<OpFoldResult> getMixedBasis() {
OpBuilder builder(getContext());
return ::mlir::getMixedValues(getStaticBasis(), getDynamicBasis(), builder);
}

/// Return a vector that contains the basis of the operation, removing
/// the outer bound if one is present.
SmallVector<OpFoldResult> getEffectiveBasis();
}];

let hasVerifier = 1;
Expand Down
14 changes: 10 additions & 4 deletions mlir/include/mlir/Dialect/Affine/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,17 +307,23 @@ struct DivModValue {
DivModValue getDivMod(OpBuilder &b, Location loc, Value lhs, Value rhs);

/// Generate the IR to delinearize `linearIndex` given the `basis` and return
/// the multi-index.
/// the multi-index. `hasOuterBound` indicates whether `basis` has an entry
/// given the size of the first multi-index result - if it is true, the function
/// will return `basis.size()` values, otherwise, it will return `basis.size() +
/// 1`.
FailureOr<SmallVector<Value>> delinearizeIndex(OpBuilder &b, Location loc,
Value linearIndex,
ArrayRef<Value> basis);
ArrayRef<Value> basis,
bool hasOuterBound = true);

FailureOr<SmallVector<Value>> delinearizeIndex(OpBuilder &b, Location loc,
Value linearIndex,
ArrayRef<OpFoldResult> basis);
ArrayRef<OpFoldResult> basis,
bool hasOuterBound = true);

// Generate IR that extracts the linear index from a multi-index according to
// a basis/shape.
// a basis/shape. The basis may contain either `multiIndex.size()` or
// `multiIndex.size() - 1` elements.
OpFoldResult linearizeIndex(ArrayRef<OpFoldResult> multiIndex,
ArrayRef<OpFoldResult> basis,
ImplicitLocOpBuilder &builder);
Expand Down
Loading
Loading