[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