[Mlir-commits] [mlir] [mlir][xegpu] Check uArch block shapes in VectorToXeGPU transfer lowering (PR #217179)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Sep 10 05:05:19 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-gpu
Author: Igor Zamyatin (Garra1980)
<details>
<summary>Changes</summary>
vector.transfer_read/transfer_write were lowered to xegpu.load_nd / xegpu.store_nd whenever the target chip was pvc, bmg or cri and the transfer had rank >= 2, without asking whether the target's subgroup 2D block instructions can access the requested tile at all.
Changing it to query the uArch 2D block load/store description up front and keep the block path only for tiles whose two innermost dims are each a multiple of a supported block size- the property the later layout propagation and blocking passes rely on when they split a tile into hardware-sized blocks. Everything else takes the scattered load_gather/store_scatter fallback that both patterns already have.
Deriving 2D block support from the uArch instruction registry also replaces the hardcoded chip list, resolving the TODO left there; the three chips that registry covers are the same three that were listed.
assisted by claude
---
Patch is 26.40 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217179.diff
4 Files Affected:
- (modified) mlir/include/mlir/Dialect/XeGPU/uArch/uArchBase.h (+17-13)
- (modified) mlir/lib/Conversion/VectorToXeGPU/VectorToXeGPU.cpp (+89-33)
- (modified) mlir/test/Conversion/VectorToXeGPU/transfer-read-to-xegpu.mlir (+69-58)
- (modified) mlir/test/Conversion/VectorToXeGPU/transfer-write-to-xegpu.mlir (+43-21)
``````````diff
diff --git a/mlir/include/mlir/Dialect/XeGPU/uArch/uArchBase.h b/mlir/include/mlir/Dialect/XeGPU/uArch/uArchBase.h
index e1879bb3ffe1c..33a56ab221396 100644
--- a/mlir/include/mlir/Dialect/XeGPU/uArch/uArchBase.h
+++ b/mlir/include/mlir/Dialect/XeGPU/uArch/uArchBase.h
@@ -165,7 +165,17 @@ struct MMAInstructionInterface {
// (width, height, count) tuples and share a packed-format bit size. The
// transform / transpose / upConv flags are only meaningful for loads; store
// and prefetch implementations ignore them.
-struct BlockIOInstructionInterface {
+struct BlockIOInstructionInterface : public Instruction {
+ BlockIOInstructionInterface(InstructionKind kind)
+ : Instruction(kind, InstructionScope::Subgroup) {}
+
+ static bool classof(const Instruction *B) {
+ InstructionKind kind = B->getInstructionKind();
+ return kind == InstructionKind::Subgroup2DBlockLoad ||
+ kind == InstructionKind::Subgroup2DBlockStore ||
+ kind == InstructionKind::Subgroup2DBlockPrefetch;
+ }
+
using BlockShapes =
std::tuple<llvm::ArrayRef<int>, llvm::ArrayRef<int>, llvm::ArrayRef<int>>;
@@ -229,11 +239,9 @@ struct StoreScatterInstruction
// subclass when it diverges from the extension defaults.
//===----------------------------------------------------------------------===//
-struct Subgroup2DBlockStoreInstruction : public Instruction,
- public BlockIOInstructionInterface {
+struct Subgroup2DBlockStoreInstruction : public BlockIOInstructionInterface {
Subgroup2DBlockStoreInstruction()
- : Instruction(InstructionKind::Subgroup2DBlockStore,
- InstructionScope::Subgroup) {}
+ : BlockIOInstructionInterface(InstructionKind::Subgroup2DBlockStore) {}
static bool classof(const Instruction *B) {
return B->getInstructionKind() == InstructionKind::Subgroup2DBlockStore;
}
@@ -259,11 +267,9 @@ struct Subgroup2DBlockStoreInstruction : public Instruction,
}
};
-struct Subgroup2DBlockLoadInstruction : public Instruction,
- public BlockIOInstructionInterface {
+struct Subgroup2DBlockLoadInstruction : public BlockIOInstructionInterface {
Subgroup2DBlockLoadInstruction()
- : Instruction(InstructionKind::Subgroup2DBlockLoad,
- InstructionScope::Subgroup) {}
+ : BlockIOInstructionInterface(InstructionKind::Subgroup2DBlockLoad) {}
static bool classof(const Instruction *B) {
return B->getInstructionKind() == InstructionKind::Subgroup2DBlockLoad;
}
@@ -326,11 +332,9 @@ struct Subgroup2DBlockLoadInstruction : public Instruction,
}
};
-struct Subgroup2DBlockPrefetchInstruction : public Instruction,
- public BlockIOInstructionInterface {
+struct Subgroup2DBlockPrefetchInstruction : public BlockIOInstructionInterface {
Subgroup2DBlockPrefetchInstruction()
- : Instruction(InstructionKind::Subgroup2DBlockPrefetch,
- InstructionScope::Subgroup) {}
+ : BlockIOInstructionInterface(InstructionKind::Subgroup2DBlockPrefetch) {}
static bool classof(const Instruction *B) {
return B->getInstructionKind() == InstructionKind::Subgroup2DBlockPrefetch;
}
diff --git a/mlir/lib/Conversion/VectorToXeGPU/VectorToXeGPU.cpp b/mlir/lib/Conversion/VectorToXeGPU/VectorToXeGPU.cpp
index 706f24128720b..05686486fe37b 100644
--- a/mlir/lib/Conversion/VectorToXeGPU/VectorToXeGPU.cpp
+++ b/mlir/lib/Conversion/VectorToXeGPU/VectorToXeGPU.cpp
@@ -21,6 +21,8 @@
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/Dialect/XeGPU/IR/XeGPU.h"
#include "mlir/Dialect/XeGPU/Utils/XeGPUUtils.h"
+#include "mlir/Dialect/XeGPU/uArch/uArchBase.h"
+#include "mlir/Dialect/XeGPU/uArch/uArchCommon.h"
#include "mlir/IR/Matchers.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
@@ -83,6 +85,53 @@ static bool isInnermostTwoDimsTransposed(AffineMap map) {
map.getResult(numResults - 1) == getAffineDimExpr(numInputs - 2, ctx);
}
+// Return true if `uArch` can transfer a `shape`-shaped tile of `elemTy`
+// elements with the subgroup 2D block instruction `instKind`.
+//
+// A 2D block instruction accesses the two innermost dimensions of the tile;
+// leading dimensions are unrolled into a sequence of 2D accesses. The
+// innermost 2D tile does not have to match a hardware block exactly, because
+// the later XeGPU passes (layout propagation and blocking) split it into
+// hardware-sized blocks - but that split only exists when each of the two
+// innermost extents is a multiple of a supported block extent. Transfers
+// without such a split cannot be expressed with block instructions at all and
+// must use a different lowering.
+static bool isSupportedBlockShape(const xegpu::uArch::uArch *uArch,
+ xegpu::uArch::InstructionKind instKind,
+ ArrayRef<int64_t> shape, Type elemTy,
+ bool hasTranspose = false) {
+ if (shape.size() < 2)
+ return false;
+ if (!uArch || !uArch->isSupportedInstruction(instKind))
+ return false;
+
+ const auto *blockInst = dyn_cast<xegpu::uArch::BlockIOInstructionInterface>(
+ uArch->getInstruction(instKind));
+ if (!blockInst)
+ return false;
+
+ // Query the untransformed shapes. Whether a tile is eventually loaded with
+ // the transformed (VNNI) variant is only decided later, when layout
+ // propagation knows whether it feeds the B operand of a matrix operation,
+ // but for 8-, 16- and 32-bit elements the transformed shapes are a subset of
+ // the untransformed ones, so nothing the transformed variant could access is
+ // rejected here. Sub-byte elements are the exception, and there accepting a
+ // transformed-only shape would just move the failure into layout
+ // propagation, which asserts once it picks the untransformed variant.
+ // A missing entry means the element type itself is not supported.
+ std::optional<xegpu::uArch::BlockIOInstructionInterface::BlockShapes>
+ blockShapes = blockInst->getBlockWidthHeightCount(
+ elemTy, /*hasTransform=*/false, hasTranspose);
+ if (!blockShapes)
+ return false;
+
+ auto [widths, heights, counts] = *blockShapes;
+ int width = static_cast<int>(shape.back());
+ int height = static_cast<int>(shape[shape.size() - 2]);
+ return xegpu::getLargestDivisor(width, widths) != -1 &&
+ xegpu::getLargestDivisor(height, heights) != -1;
+}
+
static LogicalResult transferPreconditions(PatternRewriter &rewriter,
VectorTransferOpInterface xferOp) {
if (xferOp.getMask())
@@ -554,10 +603,8 @@ struct TransferReadLowering : public OpRewritePattern<vector::TransferReadOp> {
return success();
}
- // TODO: This check needs to be replaced with proper uArch capability check.
- auto chip = xegpu::getChipStr(readOp);
- bool hasBlockLoadSupport =
- (chip == "pvc" || chip == "bmg" || chip == "cri");
+ const xegpu::uArch::uArch *uArch =
+ xegpu::uArch::getUArch(xegpu::getChipStr(readOp).value_or(""));
// An nd block load can realize a minor-identity map directly, or an
// innermost-two-dims transpose via a trailing vector.transpose. Any other
@@ -566,29 +613,36 @@ struct TransferReadLowering : public OpRewritePattern<vector::TransferReadOp> {
AffineMap readMap = readOp.getPermutationMap();
bool isTransposeLoad = isInnermostTwoDimsTransposed(readMap);
- // Prefer an nd block load. It requires HW block-load support, a vector of
- // rank >= 2 backed by a scalar-element memref, and a map the block load can
- // realize. 1D vectors use the scattered xegpu.load path instead, which has
- // a richer interface (e.g. layout capabilities). Out-of-bounds reads are
- // allowed as long as the padding matches load_nd's implicit zero padding.
+ // A block load transfers the tile as it is laid out in memory, so a
+ // transposing read describes a tile whose innermost two dims are swapped.
+ Type elementType = loadedVecTy.getElementType();
+ SmallVector<int64_t> descShape(loadedVecTy.getShape());
+ if (isTransposeLoad) {
+ size_t rank = descShape.size();
+ assert(rank >= 2 && "Transpose requires at least 2 dimensions");
+ std::swap(descShape[rank - 1], descShape[rank - 2]);
+ }
+
+ // Prefer an nd block load. It requires a vector of rank >= 2 backed by a
+ // scalar-element memref, a map the block load can realize, and a tile shape
+ // the target's 2D block load can access. 1D vectors use the scattered
+ // xegpu.load path instead, which has a richer interface (e.g. layout
+ // capabilities). Out-of-bounds reads are allowed as long as the padding
+ // matches load_nd's implicit zero padding.
bool canLowerToLoadNd =
- hasBlockLoadSupport && loadedVecTy.getRank() > 1 &&
+ loadedVecTy.getRank() > 1 &&
(readMap.isMinorIdentity() || isTransposeLoad) &&
readMemTy.getElementType().isIntOrFloat() &&
- (!isOutOfBounds || isZeroOrPoisonPadding(readOp.getPadding()));
+ (!isOutOfBounds || isZeroOrPoisonPadding(readOp.getPadding())) &&
+ isSupportedBlockShape(
+ uArch, xegpu::uArch::InstructionKind::Subgroup2DBlockLoad,
+ descShape, elementType, /*hasTranspose=*/isTransposeLoad);
if (canLowerToLoadNd) {
- auto elementType = loadedVecTy.getElementType();
-
- SmallVector<int64_t> descShape(loadedVecTy.getShape());
- if (isTransposeLoad) {
- // If load is transposed, simply swap the last two dimensions of the
- // loaded vector type to get the descriptor shape.
- size_t rank = descShape.size();
- assert(rank >= 2 && "Transpose requires at least 2 dimensions");
- std::swap(descShape[rank - 1], descShape[rank - 2]);
+ // The load produces the memory-ordered tile; the transpose below restores
+ // the shape the transfer_read asked for.
+ if (isTransposeLoad)
loadedVecTy = VectorType::get(descShape, elementType);
- }
auto descType = xegpu::TensorDescType::get(
descShape, elementType, /*array_length=*/1,
/*boundary_check=*/isOutOfBounds, xegpu::MemorySpace::Global);
@@ -673,20 +727,22 @@ struct TransferWriteLowering
return success();
}
- // TODO: This check needs to be replaced with proper uArch capability check.
- auto chip = xegpu::getChipStr(writeOp);
- bool hasBlockStoreSupport =
- (chip == "pvc" || chip == "bmg" || chip == "cri");
+ const xegpu::uArch::uArch *uArch =
+ xegpu::uArch::getUArch(xegpu::getChipStr(writeOp).value_or(""));
- // Prefer an nd block store. It requires HW block-store support, a vector of
- // rank >= 2 backed by a scalar-element memref, and a minor-identity map
- // (block stores have no transpose support). 1D vectors use the scattered
- // xegpu.store path instead, which has a richer interface. Out-of-bounds
- // writes are handled by the descriptor's boundary check.
+ // Prefer an nd block store. It requires a vector of rank >= 2 backed by a
+ // scalar-element memref, a minor-identity map (block stores have no
+ // transpose support), and a tile shape the target's 2D block store can
+ // access. 1D vectors use the scattered xegpu.store path instead, which has
+ // a richer interface. Out-of-bounds writes are handled by the descriptor's
+ // boundary check.
AffineMap map = writeOp.getPermutationMap();
- bool canLowerToStoreNd = hasBlockStoreSupport && vecTy.getRank() > 1 &&
- map.isMinorIdentity() &&
- writeMemTy.getElementType().isIntOrFloat();
+ bool canLowerToStoreNd =
+ vecTy.getRank() > 1 && map.isMinorIdentity() &&
+ writeMemTy.getElementType().isIntOrFloat() &&
+ isSupportedBlockShape(
+ uArch, xegpu::uArch::InstructionKind::Subgroup2DBlockStore,
+ vecTy.getShape(), vecTy.getElementType());
if (canLowerToStoreNd) {
auto [src, indices] = convertMemrefAndOffsetsToTargetRank(
diff --git a/mlir/test/Conversion/VectorToXeGPU/transfer-read-to-xegpu.mlir b/mlir/test/Conversion/VectorToXeGPU/transfer-read-to-xegpu.mlir
index 1464aa1ace477..dc2eddfb985c8 100644
--- a/mlir/test/Conversion/VectorToXeGPU/transfer-read-to-xegpu.mlir
+++ b/mlir/test/Conversion/VectorToXeGPU/transfer-read-to-xegpu.mlir
@@ -338,6 +338,31 @@ gpu.func @load_high_dim_vector(%source: memref<16x32x64xf32>,
// LOAD-GATHER: %[[VEC:.+]] = xegpu.load %[[COLLAPSE_I]][%[[IDX]]], %[[CST]] : i64, vector<8x16x32xindex>, vector<8x16x32xi1> -> vector<8x16x32xf32>
}
+// -----
+// The unit innermost dimension leaves a 16x1 f16 tile to transfer, and a 2D
+// block load needs a block width that is a multiple of 16 f16 elements. The
+// read must use the scattered path rather than a block load whose shape no
+// hardware instruction can provide.
+gpu.module @xevm_module {
+gpu.func @load_high_dim_unsupported_block_width(%source: memref<1x24x1024x1xf16>,
+ %offset: index) -> vector<1x8x16x1xf16> {
+ %pad = ub.poison : f16
+ %c0 = arith.constant 0 : index
+ %0 = vector.transfer_read %source[%c0, %offset, %offset, %c0], %pad
+ {in_bounds = [true, true, true, true]}
+ : memref<1x24x1024x1xf16>, vector<1x8x16x1xf16>
+ gpu.return %0 : vector<1x8x16x1xf16>
+}
+
+// CHECK-LABEL: @load_high_dim_unsupported_block_width(
+// CHECK-SAME: %[[SRC:.+]]: memref<1x24x1024x1xf16>,
+// CHECK-NOT: xegpu.create_nd_tdesc
+// CHECK-NOT: xegpu.load_nd
+// CHECK: %[[COLLAPSE:.+]] = memref.extract_aligned_pointer_as_index %[[SRC]] : memref<1x24x1024x1xf16> -> index
+// CHECK: %[[COLLAPSE_I:.+]] = arith.index_cast %[[COLLAPSE]] : index to i64
+// CHECK: %[[VEC:.+]] = xegpu.load %[[COLLAPSE_I]][%{{.+}}], %{{.+}} : i64, vector<1x8x16x1xindex>, vector<1x8x16x1xi1> -> vector<1x8x16x1xf16>
+}
+
// -----
gpu.module @xevm_module {
gpu.func @load_8D_vector(%source: memref<2x2x2x2x2x2x2x2xf32>,
@@ -348,25 +373,22 @@ gpu.func @load_8D_vector(%source: memref<2x2x2x2x2x2x2x2xf32>,
gpu.return %0 : vector<2x2x2x2x2x2x2x2xf32>
}
-// LOAD-ND-LABEL: @load_8D_vector(
-// LOAD-ND-SAME: %[[SRC:.+]]: memref<2x2x2x2x2x2x2x2xf32>,
-// LOAD-ND: %[[DESC:.+]] = xegpu.create_nd_tdesc %[[SRC]] : memref<2x2x2x2x2x2x2x2xf32>
-// LOAD-ND-SAME: -> !xegpu.tensor_desc<2x2x2x2x2x2x2x2xf32, #xegpu.block_tdesc_attr<boundary_check = false>>
-// LOAD-ND: %[[VEC:.+]] = xegpu.load_nd %[[DESC]]
-// LOAD-ND-SAME: -> vector<2x2x2x2x2x2x2x2xf32>
-
-// LOAD-GATHER-LABEL: @load_8D_vector(
-// LOAD-GATHER-SAME: %[[SRC:.+]]: memref<2x2x2x2x2x2x2x2xf32>,
-// LOAD-GATHER: %[[CST:.+]] = arith.constant dense<true> : vector<2x2x2x2x2x2x2x2xi1>
-// LOAD-GATHER-COUNT8: vector.step
-// LOAD-GATHER-COUNT7: vector.shape_cast
-// LOAD-GATHER-COUNT8: vector.broadcast {{.*}} : vector<2x2x2x2x2x2x2x2xindex>
-// LOAD-GATHER-COUNT7: arith.addi {{.*}} : vector<2x2x2x2x2x2x2x2xindex>
-// LOAD-GATHER: %[[SPLAT:.+]] = vector.broadcast {{.*}} : index to vector<2x2x2x2x2x2x2x2xindex>
-// LOAD-GATHER: %[[IDX:.+]] = arith.addi %[[SPLAT]], {{.*}} : vector<2x2x2x2x2x2x2x2xindex>
-// LOAD-GATHER: %[[COLLAPSE:.+]] = memref.extract_aligned_pointer_as_index %[[SRC]] : memref<2x2x2x2x2x2x2x2xf32> -> index
-// LOAD-GATHER: %[[COLLAPSE_I:.+]] = arith.index_cast %[[COLLAPSE]] : index to i64
-// LOAD-GATHER: %[[VEC:.+]] = xegpu.load %[[COLLAPSE_I]][%[[IDX]]], %[[CST]] : i64, vector<2x2x2x2x2x2x2x2xindex>, vector<2x2x2x2x2x2x2x2xi1> -> vector<2x2x2x2x2x2x2x2xf32>
+// The innermost extent of 2 f32 elements is not a multiple of any block width
+// the 2D block load supports (16 f32 elements), so the read uses the scattered
+// path on every target.
+// CHECK-LABEL: @load_8D_vector(
+// CHECK-SAME: %[[SRC:.+]]: memref<2x2x2x2x2x2x2x2xf32>,
+// CHECK-NOT: xegpu.load_nd
+// CHECK: %[[CST:.+]] = arith.constant dense<true> : vector<2x2x2x2x2x2x2x2xi1>
+// CHECK-COUNT-8: vector.step
+// CHECK-COUNT-7: vector.shape_cast
+// CHECK-COUNT-8: vector.broadcast {{.*}} to vector<2x2x2x2x2x2x2x2xindex>
+// CHECK-COUNT-7: arith.addi {{.*}} : vector<2x2x2x2x2x2x2x2xindex>
+// CHECK: %[[SPLAT:.+]] = vector.broadcast {{.*}} : index to vector<2x2x2x2x2x2x2x2xindex>
+// CHECK: %[[IDX:.+]] = arith.addi %[[SPLAT]], {{.*}} : vector<2x2x2x2x2x2x2x2xindex>
+// CHECK: %[[COLLAPSE:.+]] = memref.extract_aligned_pointer_as_index %[[SRC]] : memref<2x2x2x2x2x2x2x2xf32> -> index
+// CHECK: %[[COLLAPSE_I:.+]] = arith.index_cast %[[COLLAPSE]] : index to i64
+// CHECK: %[[VEC:.+]] = xegpu.load %[[COLLAPSE_I]][%[[IDX]]], %[[CST]] : i64, vector<2x2x2x2x2x2x2x2xindex>, vector<2x2x2x2x2x2x2x2xi1> -> vector<2x2x2x2x2x2x2x2xf32>
}
// -----
@@ -380,23 +402,21 @@ gpu.func @load_transpose_f16(%source: memref<32x64xf16>,
gpu.return %0 : vector<8x16xf16>
}
-// LOAD-ND-LABEL: @load_transpose_f16(
-// LOAD-ND: %[[LOAD:.*]] = xegpu.load_nd
-// LOAD-ND: vector.transpose %[[LOAD]], [1, 0] : vector<16x8xf16> to vector<8x16xf16>
-
-// LOAD-GATHER-LABEL: @load_transpose_f16(
-// LOAD-GATHER-SAME: %[[SRC:.+]]: memref<32x64xf16>,
-// LOAD-GATHER: %[[CST:.+]] = arith.constant dense<true> : vector<8x16xi1>
-// LOAD-GATHER-COUNT2: vector.step
-// LOAD-GATHER-COUNT2: vector.shape_cast
-// LOAD-GATHER-COUNT2: vector.broadcast
-// LOAD-GATHER-COUNT2: arith.muli {{.*}} : index
-// LOAD-GATHER-COUNT2: arith.addi {{.*}} : index
-// LOAD-GATHER: %[[BCAST2:.+]] = vector.broadcast {{.*}} : index to vector<8x16xindex>
-// LOAD-GATHER: %[[IDX:.+]] = arith.addi %[[BCAST2]], {{.*}}: vector<8x16xindex>
-// LOAD-GATHER: %[[COLLAPSE:.*]] = memref.extract_aligned_pointer_as_index %arg0 : memref<32x64xf16> -> index
-// LOAD-GATHER: %[[COLLAPSE_I:.+]] = arith.index_cast %[[COLLAPSE]] : index to i64
-// LOAD-GATHER: %[[LOAD:.*]] = xegpu.load %[[COLLAPSE_I]][%[[IDX]]], %[[CST]] : i64, vector<8x16xindex>, vector<8x16xi1> -> vector<8x16xf16>
+// Unlike the f32 case above, a transposing block load of 16-bit elements needs
+// a block 16 elements wide, so the 16x8 tile this read would have to fetch has
+// no hardware block shape and the read uses the scattered path instead.
+// CHECK-LABEL: @load_transpose_f16(
+// CHECK-SAME: %[[SRC:.+]]: memref<32x64xf16>,
+// CHECK-NOT: xegpu.load_nd
+// CHECK: %[[CST:.+]] = arith.constant dense<true> : vector<8x16xi1>
+// CHECK-COUNT-2: vector.step
+// CHECK: vector.shape_cast
+// CHECK-COUNT-2: vector.broadcast {{.*}} to vector<8x16xindex>
+// CHECK: %[[BCAST2:.+]] = vector.broadcast {{.*}} : index to vector<8x16xindex>
+// CHECK: %[[IDX:.+]] = arith.addi %[[BCAST2]], {{.*}}: vector<8x16xindex>
+// CHECK: %[[COLLAPSE:.*]] = memref.extract_aligned_pointer_as_index %arg0 : memref<32x64xf16> -> index
+// CHECK: %[[COLLAPSE_I:.+]] = arith.index_cast %[[COLLAPSE]] : index to i64
+// CHECK: %[[LOAD:.*]] = xegpu.load %[[COLLAPSE_I]][%[[IDX]]], %[[CST]] : i64, vector<8x16xindex>, vector<8x16xi1> -> vector<8x16xf16>
}
// -----
@@ -679,34 +699,25 @@ gpu.func @transpose_1x1024x24x64(
// The vector.transpose is folded into the transfer_read via
// CombineTransferReadOpTranspose, giving the read a mid-vector permutation map
// (d0, d1, d2, d3) -> (d0, d2, d1, d3). An nd block load can only realize an
-// innermost-two-dims transpose, so the read falls back to the scattered path
-// while the identity-map write still lowers to store_nd.
-// LOAD-ND-LABEL: @transpose_1x1024x24x64
-// LOAD-ND-DAG: %[[C1536:.+]] = arith.constant 1536 : index
-// LOAD-ND-DAG: %[[C64:.+]] = arith.constant 64 : index
-// LOAD-ND: arith.muli %{{.+}}, %[[C1536]] : index
-// LOAD-ND: arith.muli %block_id_x, %[[C64]] : index
-// LOAD-ND: %[[VEC:.+]] = xegpu.load {{.*}} -> vector<1x1x16x8xf16>
-// LOAD-ND: %[[WDESC:.+]] = xegpu.create_nd_tdesc %arg1 : memref<1x24x1024x64xf16>
-// LOAD-ND-SAME: -> !xegpu.tensor_desc<1x1x16x8xf16, #xegpu.block_tdesc_attr<boundary_check = false>>
-// LOAD-ND: xegpu.store_nd %[[VEC]], %[[WDESC]]
-
-// LOAD-GATHER-LABEL: @transpose_1x1024x24x64
-// LOAD-GATHER-DAG: %[[C1536:.+]] = arith.constant 1536 : index
-// LOAD-GATHER-DAG: %[[C64:.+]] = arith.cons...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/217179
More information about the Mlir-commits
mailing list