Skip to content

Commit

Permalink
[mlir] make memref.subview produce strided layout
Browse files Browse the repository at this point in the history
Memref subview operation has been initially designed to work on memrefs with
strided layouts only and has never supported anything else. Port it to use the
recently added StridedLayoutAttr instead of extracting the strided from
implicitly from affine maps.

Reviewed By: nicolasvasilache

Differential Revision: https://reviews.llvm.org/D133938
  • Loading branch information
ftynse committed Sep 16, 2022
1 parent 57c7bb3 commit 2791162
Show file tree
Hide file tree
Showing 19 changed files with 266 additions and 335 deletions.
22 changes: 6 additions & 16 deletions mlir/include/mlir/IR/BuiltinTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,28 +413,18 @@ inline bool TensorType::classof(Type type) {
/// MemRefs with a layout map in strided form include:
/// 1. empty or identity layout map, in which case the stride information is
/// the canonical form computed from sizes;
/// 2. single affine map layout of the form `K + k0 * d0 + ... kn * dn`,
/// where K and ki's are constants or symbols.
/// 2. a StridedLayoutAttr layout;
/// 3. any other layout that be converted into a single affine map layout of
/// the form `K + k0 * d0 + ... kn * dn`, where K and ki's are constants or
/// symbols.
///
/// A stride specification is a list of integer values that are either static
/// or dynamic (encoded with getDynamicStrideOrOffset()). Strides encode the
/// distance in the number of elements between successive entries along a
/// or dynamic (encoded with ShapedType::kDynamicStrideOrOffset). Strides encode
/// the distance in the number of elements between successive entries along a
/// particular dimension.
///
/// For example, `memref<42x16xf32, (64 * d0 + d1)>` specifies a view into a
/// non-contiguous memory region of `42` by `16` `f32` elements in which the
/// distance between two consecutive elements along the outer dimension is `1`
/// and the distance between two consecutive elements along the inner dimension
/// is `64`.
///
/// The convention is that the strides for dimensions d0, .. dn appear in
/// order to make indexing intuitive into the result.
LogicalResult getStridesAndOffset(MemRefType t,
SmallVectorImpl<int64_t> &strides,
int64_t &offset);
LogicalResult getStridesAndOffset(MemRefType t,
SmallVectorImpl<AffineExpr> &strides,
AffineExpr &offset);

/// Return a version of `t` with identity layout if it can be determined
/// statically that the layout is the canonical contiguous strided layout.
Expand Down
59 changes: 36 additions & 23 deletions mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2184,11 +2184,10 @@ Type SubViewOp::inferResultType(MemRefType sourceMemRefType,
}

// The type is now known.
return MemRefType::get(
staticSizes, sourceMemRefType.getElementType(),
makeStridedLinearLayoutMap(targetStrides, targetOffset,
sourceMemRefType.getContext()),
sourceMemRefType.getMemorySpace());
return MemRefType::get(staticSizes, sourceMemRefType.getElementType(),
StridedLayoutAttr::get(sourceMemRefType.getContext(),
targetOffset, targetStrides),
sourceMemRefType.getMemorySpace());
}

Type SubViewOp::inferResultType(MemRefType sourceMemRefType,
Expand Down Expand Up @@ -2224,14 +2223,19 @@ Type SubViewOp::inferRankReducedResultType(ArrayRef<int64_t> resultShape,
Optional<llvm::SmallDenseSet<unsigned>> dimsToProject =
computeRankReductionMask(inferredType.getShape(), resultShape);
assert(dimsToProject.has_value() && "invalid rank reduction");
llvm::SmallBitVector dimsToProjectVector(inferredType.getRank());
for (unsigned dim : *dimsToProject)
dimsToProjectVector.set(dim);

// Compute layout map and result type.
AffineMap map = getProjectedMap(inferredType.getLayout().getAffineMap(),
dimsToProjectVector);
return MemRefType::get(resultShape, inferredType.getElementType(), map,

// Compute the layout and result type.
auto inferredLayout = inferredType.getLayout().cast<StridedLayoutAttr>();
SmallVector<int64_t> rankReducedStrides;
rankReducedStrides.reserve(resultShape.size());
for (auto [idx, value] : llvm::enumerate(inferredLayout.getStrides())) {
if (!dimsToProject->contains(idx))
rankReducedStrides.push_back(value);
}
return MemRefType::get(resultShape, inferredType.getElementType(),
StridedLayoutAttr::get(inferredLayout.getContext(),
inferredLayout.getOffset(),
rankReducedStrides),
inferredType.getMemorySpace());
}

Expand Down Expand Up @@ -2363,8 +2367,8 @@ Value SubViewOp::getViewSource() { return getSource(); }
/// Return true if t1 and t2 have equal offsets (both dynamic or of same
/// static value).
static bool haveCompatibleOffsets(MemRefType t1, MemRefType t2) {
AffineExpr t1Offset, t2Offset;
SmallVector<AffineExpr> t1Strides, t2Strides;
int64_t t1Offset, t2Offset;
SmallVector<int64_t> t1Strides, t2Strides;
auto res1 = getStridesAndOffset(t1, t1Strides, t1Offset);
auto res2 = getStridesAndOffset(t2, t2Strides, t2Offset);
return succeeded(res1) && succeeded(res2) && t1Offset == t2Offset;
Expand Down Expand Up @@ -2506,16 +2510,25 @@ static MemRefType getCanonicalSubViewResultType(
// Return nullptr as failure mode.
if (!unusedDims)
return nullptr;
SmallVector<int64_t> shape;
for (const auto &sizes : llvm::enumerate(nonRankReducedType.getShape())) {
if (unusedDims->test(sizes.index()))

auto layout = nonRankReducedType.getLayout().cast<StridedLayoutAttr>();
SmallVector<int64_t> shape, strides;
unsigned numDimsAfterReduction =
nonRankReducedType.getRank() - unusedDims->count();
shape.reserve(numDimsAfterReduction);
strides.reserve(numDimsAfterReduction);
for (const auto &[idx, size, stride] :
llvm::zip(llvm::seq<unsigned>(0, nonRankReducedType.getRank()),
nonRankReducedType.getShape(), layout.getStrides())) {
if (unusedDims->test(idx))
continue;
shape.push_back(sizes.value());
shape.push_back(size);
strides.push_back(stride);
}
AffineMap layoutMap = nonRankReducedType.getLayout().getAffineMap();
if (!layoutMap.isIdentity())
layoutMap = getProjectedMap(layoutMap, *unusedDims);
return MemRefType::get(shape, nonRankReducedType.getElementType(), layoutMap,

return MemRefType::get(shape, nonRankReducedType.getElementType(),
StridedLayoutAttr::get(sourceType.getContext(),
layout.getOffset(), strides),
nonRankReducedType.getMemorySpace());
}

Expand Down
40 changes: 31 additions & 9 deletions mlir/lib/IR/BuiltinTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,9 +766,22 @@ static LogicalResult extractStrides(AffineExpr e,
llvm_unreachable("unexpected binary operation");
}

LogicalResult mlir::getStridesAndOffset(MemRefType t,
SmallVectorImpl<AffineExpr> &strides,
AffineExpr &offset) {
/// A stride specification is a list of integer values that are either static
/// or dynamic (encoded with ShapedType::kDynamicStrideOrOffset). Strides encode
/// the distance in the number of elements between successive entries along a
/// particular dimension.
///
/// For example, `memref<42x16xf32, (64 * d0 + d1)>` specifies a view into a
/// non-contiguous memory region of `42` by `16` `f32` elements in which the
/// distance between two consecutive elements along the outer dimension is `1`
/// and the distance between two consecutive elements along the inner dimension
/// is `64`.
///
/// The convention is that the strides for dimensions d0, .. dn appear in
/// order to make indexing intuitive into the result.
static LogicalResult getStridesAndOffset(MemRefType t,
SmallVectorImpl<AffineExpr> &strides,
AffineExpr &offset) {
AffineMap m = t.getLayout().getAffineMap();

if (m.getNumResults() != 1 && !m.isIdentity())
Expand Down Expand Up @@ -807,12 +820,12 @@ LogicalResult mlir::getStridesAndOffset(MemRefType t,
for (auto &stride : strides)
stride = simplifyAffineExpr(stride, numDims, numSymbols);

/// In practice, a strided memref must be internally non-aliasing. Test
/// against 0 as a proxy.
/// TODO: static cases can have more advanced checks.
/// TODO: dynamic cases would require a way to compare symbolic
/// expressions and would probably need an affine set context propagated
/// everywhere.
// In practice, a strided memref must be internally non-aliasing. Test
// against 0 as a proxy.
// TODO: static cases can have more advanced checks.
// TODO: dynamic cases would require a way to compare symbolic
// expressions and would probably need an affine set context propagated
// everywhere.
if (llvm::any_of(strides, [](AffineExpr e) {
return e == getAffineConstantExpr(0, e.getContext());
})) {
Expand All @@ -827,6 +840,15 @@ LogicalResult mlir::getStridesAndOffset(MemRefType t,
LogicalResult mlir::getStridesAndOffset(MemRefType t,
SmallVectorImpl<int64_t> &strides,
int64_t &offset) {
// Happy path: the type uses the strided layout directly.
if (auto strided = t.getLayout().dyn_cast<StridedLayoutAttr>()) {
llvm::append_range(strides, strided.getStrides());
offset = strided.getOffset();
return success();
}

// Otherwise, defer to the affine fallback as layouts are supposed to be
// convertible to affine maps.
AffineExpr offsetExpr;
SmallVector<AffineExpr, 4> strideExprs;
if (failed(::getStridesAndOffset(t, strideExprs, offsetExpr)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,11 @@ func.func @main(%t: tensor<5xf32>) -> (f32, f32) {
// -----

// CHECK: #[[$map2a:.*]] = affine_map<(d0, d1)[s0, s1, s2] -> (d0 * s1 + s0 + d1 * s2)>
// CHECK: #[[$map2b:.*]] = affine_map<(d0, d1)[s0] -> (d0 * 20 + s0 + d1)>
// CHECK-LABEL: func @callee(
// CHECK-SAME: %{{.*}}: index,
// CHECK-SAME: %[[r:.*]]: memref<2x5xf32, #[[$map2a]]>) {
// CHECK: %[[alloc:.*]] = memref.alloc() {{.*}} : memref<10x20xf32>
// CHECK: %[[subview:.*]] = memref.subview %[[alloc]]{{.*}} : memref<10x20xf32> to memref<2x5xf32, #[[$map2b]]>
// CHECK: %[[subview:.*]] = memref.subview %[[alloc]]{{.*}} : memref<10x20xf32> to memref<2x5xf32, strided<[20, 1], offset: ?>>
// CHECK: %[[casted:.*]] = memref.cast %[[subview]]
// CHECK: memref.copy %[[casted]], %[[r]]
// CHECK: memref.dealloc %[[alloc]]
Expand All @@ -98,9 +97,8 @@ func.func @main(%t: tensor<5xf32>) -> (f32, f32) {
// CHECK-NO-LAYOUT: memref.copy %[[alloc2]], %[[r]]
// CHECK-NO-LAYOUT: memref.dealloc %[[alloc2]]

// CHECK-BASELINE: #[[$map2:.*]] = affine_map<(d0, d1)[s0] -> (d0 * 20 + s0 + d1)>
// CHECK-BASELINE-LABEL: func @callee(
// CHECK-BASELINE-SAME: %{{.*}}: index) -> memref<2x5xf32, #[[$map2]]> {
// CHECK-BASELINE-SAME: %{{.*}}: index) -> memref<2x5xf32, strided<[20, 1], offset: ?>> {
// CHECK-BASELINE: %[[alloc:.*]] = memref.alloc() {{.*}} : memref<10x20xf32>
// CHECK-BASELINE: %[[subview:.*]] = memref.subview %[[alloc]]
// CHECK-BASELINE: return %[[subview]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,20 @@ func.func private @external_func_with_return_val(tensor<4xi32>) -> f32

// A function that returns a non-equivalent tensor with layout map.

// CHECK: #[[$map2:.*]] = affine_map<(d0, d1)[s0] -> (d0 * 10 + s0 + d1)>
// CHECK-LABEL: func @return_extract_slice(%{{.*}}) -> memref<2x?xf32,
// CHECK-SAME: #[[$map2]]> {
// CHECK-LABEL: func @return_extract_slice(%{{.*}}) -> memref<2x?xf32, strided<[10, 1], offset: ?>>
// CHECK: %[[alloc:.*]] = memref.alloc() {{.*}} : memref<20x10xf32>
// CHECK: %[[subview:.*]] = memref.subview {{.*}} : memref<20x10xf32> to memref<2x?xf32, #[[$map2]]>
// CHECK: %[[subview:.*]] = memref.subview {{.*}} : memref<20x10xf32> to memref<2x?xf32, strided<[10, 1], offset: ?>>
// CHECK: return %[[subview]]

// CHECK-NO-LAYOUT-MAP: #[[$map2:.*]] = affine_map<(d0, d1)[s0] -> (d0 * 10 + s0 + d1)>
// CHECK-NO-LAYOUT-MAP-LABEL: func @return_extract_slice(%{{.*}}) -> memref<2x?xf32>
// CHECK-NO-LAYOUT-MAP: %[[alloc:.*]] = memref.alloc() {{.*}} : memref<20x10xf32>
// CHECK-NO-LAYOUT-MAP: %[[subview:.*]] = memref.subview {{.*}} : memref<20x10xf32> to memref<2x?xf32, #[[$map2]]>
// CHECK-NO-LAYOUT-MAP: %[[subview:.*]] = memref.subview {{.*}} : memref<20x10xf32> to memref<2x?xf32, strided<[10, 1], offset: ?>>
// CHECK-NO-LAYOUT-MAP: %[[alloc_no_layout:.*]] = memref.alloc(%{{.*}}) : memref<2x?xf32>
// CHECK-NO-LAYOUT-MAP: memref.copy %[[subview]], %[[alloc_no_layout]]
// CHECK-NO-LAYOUT-MAP: memref.dealloc %[[alloc]]
// CHECK-NO-LAYOUT-MAP: return %[[alloc_no_layout]]

// CHECK-FULLY-DYNAMIC-LAYOUT-MAP: #[[$map2a:.*]] = affine_map<(d0, d1)[s0, s1, s2] -> (d0 * s1 + s0 + d1 * s2)>
// CHECK-FULLY-DYNAMIC-LAYOUT-MAP: #[[$map2b:.*]] = affine_map<(d0, d1)[s0] -> (d0 * 10 + s0 + d1)>
// CHECK-FULLY-DYNAMIC-LAYOUT-MAP-LABEL: func @return_extract_slice(%{{.*}}) -> memref<2x?xf32,
// CHECK-FULLY-DYNAMIC-LAYOUT-MAP-SAME: #[[$map2a]]> {
func.func @return_extract_slice(%idx: index, %sz: index) -> (tensor<2x?xf32>)
Expand Down Expand Up @@ -375,11 +371,11 @@ func.func @scf_for_with_tensor_insert_slice(
-> (tensor<?xf32>, tensor<?xf32>)
{
// CHECK-NEXT: %[[SVA:.*]] = memref.subview %[[A]]
// CHECK-NEXT: memref.copy %[[C]], %[[SVA]] : memref<4xf32, #[[$DYN_1D_MAP]]> to memref<4xf32, #[[$DYN_1D_MAP]]>
// CHECK-NEXT: memref.copy %[[C]], %[[SVA]] : memref<4xf32, #[[$DYN_1D_MAP]]> to memref<4xf32, strided<[?], offset: ?>>
%ttA = tensor.insert_slice %C into %tA[%i][4][1] : tensor<4xf32> into tensor<?xf32>

// CHECK-NEXT: %[[SVB:.*]] = memref.subview %[[B]]
// CHECK-NEXT: memref.copy %[[C]], %[[SVB]] : memref<4xf32, #[[$DYN_1D_MAP]]> to memref<4xf32, #[[$DYN_1D_MAP]]>
// CHECK-NEXT: memref.copy %[[C]], %[[SVB]] : memref<4xf32, #[[$DYN_1D_MAP]]> to memref<4xf32, strided<[?], offset: ?>>
%ttB = tensor.insert_slice %C into %tB[%i][4][1] : tensor<4xf32> into tensor<?xf32>

// scf.yield is empty and is elided
Expand Down
30 changes: 14 additions & 16 deletions mlir/test/Dialect/Linalg/promote.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#map2 = affine_map<(d0) -> (d0 + 4)>
#map3 = affine_map<(d0) -> (d0 + 3)>

// CHECK-DAG: #[[$strided2D:.*]] = affine_map<(d0, d1)[s0, s1] -> (d0 * s1 + s0 + d1)>

func.func @matmul_f32(%A: memref<?xi8>, %M: index, %N: index, %K: index) {
%c4 = arith.constant 4 : index
%c3 = arith.constant 3 : index
Expand Down Expand Up @@ -44,24 +42,24 @@ func.func @matmul_f32(%A: memref<?xi8>, %M: index, %N: index, %K: index) {
///
// CHECK: %[[tmpA:.*]] = memref.alloca() : memref<32xi8>
// CHECK: %[[fullA:.*]] = memref.view %[[tmpA]][{{.*}}][{{.*}}] : memref<32xi8> to memref<?x?xf32>
// CHECK: %[[partialA:.*]] = memref.subview %[[fullA]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: %[[partialA:.*]] = memref.subview %[[fullA]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, strided<[?, 1], offset: ?>>
///
// CHECK: %[[tmpB:.*]] = memref.alloca() : memref<48xi8>
// CHECK: %[[fullB:.*]] = memref.view %[[tmpB]][{{.*}}][{{.*}}] : memref<48xi8> to memref<?x?xf32>
// CHECK: %[[partialB:.*]] = memref.subview %[[fullB]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: %[[partialB:.*]] = memref.subview %[[fullB]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, strided<[?, 1], offset: ?>>
///
// CHECK: %[[tmpC:.*]] = memref.alloca() : memref<24xi8>
// CHECK: %[[fullC:.*]] = memref.view %[[tmpC]][{{.*}}][{{.*}}] : memref<24xi8> to memref<?x?xf32>
// CHECK: %[[partialC:.*]] = memref.subview %[[fullC]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: %[[partialC:.*]] = memref.subview %[[fullC]]{{.*}} : memref<?x?xf32> to memref<?x?xf32, strided<[?, 1], offset: ?>>

// CHECK: memref.copy %[[vA]], %[[partialA]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: memref.copy %[[vB]], %[[partialB]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: memref.copy %[[vC]], %[[partialC]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, #[[$strided2D]]>
// CHECK: memref.copy %[[vA]], %[[partialA]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, strided<[?, 1], offset: ?>>
// CHECK: memref.copy %[[vB]], %[[partialB]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, strided<[?, 1], offset: ?>>
// CHECK: memref.copy %[[vC]], %[[partialC]] : memref<?x?xf32, strided<[?, 1], offset: ?>> to memref<?x?xf32, strided<[?, 1], offset: ?>>
//
// CHECK: linalg.matmul ins(%[[partialA]], %[[partialB]]{{.*}} outs(%[[partialC]]
//
// CHECK: memref.copy %[[partialC]], %[[vC]] :
// CHECK: memref<?x?xf32, #[[$strided2D]]> to
// CHECK: memref<?x?xf32, strided<[?, 1], offset: ?>> to
// CHECK: memref<?x?xf32, strided<[?, 1], offset: ?>>
//
// CHECK-NOT: memref.dealloc %[[tmpA]] : memref<32xi8>
Expand Down Expand Up @@ -117,24 +115,24 @@ func.func @matmul_f64(%A: memref<?xi8>, %M: index, %N: index, %K: index) {
///
// CHECK: %[[tmpA_f64:.*]] = memref.alloc() : memref<64xi8>
// CHECK: %[[fullA_f64:.*]] = memref.view %[[tmpA_f64]][{{.*}}][{{.*}}] : memref<64xi8> to memref<?x?xf64>
// CHECK: %[[partialA_f64:.*]] = memref.subview %[[fullA_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: %[[partialA_f64:.*]] = memref.subview %[[fullA_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, strided<[?, 1], offset: ?>>
///
// CHECK: %[[tmpB_f64:.*]] = memref.alloc() : memref<96xi8>
// CHECK: %[[fullB_f64:.*]] = memref.view %[[tmpB_f64]][{{.*}}][{{.*}}] : memref<96xi8> to memref<?x?xf64>
// CHECK: %[[partialB_f64:.*]] = memref.subview %[[fullB_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: %[[partialB_f64:.*]] = memref.subview %[[fullB_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, strided<[?, 1], offset: ?>>
///
// CHECK: %[[tmpC_f64:.*]] = memref.alloc() : memref<48xi8>
// CHECK: %[[fullC_f64:.*]] = memref.view %[[tmpC_f64]][{{.*}}][{{.*}}] : memref<48xi8> to memref<?x?xf64>
// CHECK: %[[partialC_f64:.*]] = memref.subview %[[fullC_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: %[[partialC_f64:.*]] = memref.subview %[[fullC_f64]][0, 0] [%{{.*}}, %{{.*}}] [1, 1] : memref<?x?xf64> to memref<?x?xf64, strided<[?, 1], offset: ?>>

// CHECK: memref.copy %[[vA_f64]], %[[partialA_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: memref.copy %[[vB_f64]], %[[partialB_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: memref.copy %[[vC_f64]], %[[partialC_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, #[[$strided2D]]>
// CHECK: memref.copy %[[vA_f64]], %[[partialA_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, strided<[?, 1], offset: ?>>
// CHECK: memref.copy %[[vB_f64]], %[[partialB_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, strided<[?, 1], offset: ?>>
// CHECK: memref.copy %[[vC_f64]], %[[partialC_f64]] : memref<?x?xf64, strided<[?, 1], offset: ?>> to memref<?x?xf64, strided<[?, 1], offset: ?>>
//
// CHECK: linalg.matmul ins(%[[partialA_f64]], %[[partialB_f64]]{{.*}} outs(%[[partialC_f64]]
//
// CHECK: memref.copy %[[partialC_f64]], %[[vC_f64]] :
// CHECK: memref<?x?xf64, #[[$strided2D]]> to
// CHECK: memref<?x?xf64, strided<[?, 1], offset: ?>> to
// CHECK: memref<?x?xf64, strided<[?, 1], offset: ?>>
//
// CHECK: memref.dealloc %[[tmpA_f64]] : memref<64xi8>
Expand Down
Loading

0 comments on commit 2791162

Please sign in to comment.