[Mlir-commits] [mlir] [mlir][nvgpu] Expose which memrefs optimize-shared-memory can swizzle (NFC) (PR #237180)
Aditya Dandwate
llvmlistbot at llvm.org
Fri Oct 30 04:41:56 PDT 2026
https://github.com/adityadandwate updated https://github.com/llvm/llvm-project/pull/237180
>From 13bb80d9f21893f12780b637e43f82231d00bdb8 Mon Sep 17 00:00:00 2001
From: aditya dandwate <adandwate at polymagelabs.com>
Date: Fri, 30 Oct 2026 16:50:34 +0530
Subject: [PATCH 1/2] [mlir][nvgpu] Don't swizzle shared memory rows that
aren't a power of 2
`nvgpu-optimize-shared-memory` miscompiles memrefs whose rows aren't a power
of 2 long: the swizzle moves some accesses out of their row and into the next
one, so data is overwritten.
For example, in `memref<64x96xf16, 3>` each row holds 12 128-bit vectors, and
vector `c` of row `r` is moved to `c ^ (r & 7)`. Vector 8 of row 4 and vector 5
of row 5 then both land at vector 60 of the buffer, so one store overwrites the
other and a later load of either returns the wrong value. Vector 8 of row 63
lands at vector 771, past the end of the 768-vector buffer.
Skip such memrefs and leave their accesses unswizzled.
Assisted-by: Claude
---
.../NVGPU/Transforms/OptimizeSharedMemory.cpp | 6 ++++
.../Dialect/NVGPU/optimize-shared-memory.mlir | 29 +++++++++++++++++++
2 files changed, 35 insertions(+)
diff --git a/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp b/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
index e06b15febf145e..ac3102d4b1815b 100644
--- a/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
+++ b/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
@@ -182,6 +182,12 @@ mlir::nvgpu::optimizeSharedMemoryReadsAndWrites(Operation *parentOp,
if (rowsPerLine >= threadGroupSize)
return failure();
+ // The XOR swizzle stays within a row only if the row size is a power of 2.
+ // Otherwise it moves some accesses into the next row, where they overwrite
+ // other elements.
+ if (!llvm::isPowerOf2_64(rowSize))
+ return failure();
+
// Get sets of operations within the function that read/write to shared
// memory.
SmallVector<Operation *, 16> shmReadOps;
diff --git a/mlir/test/Dialect/NVGPU/optimize-shared-memory.mlir b/mlir/test/Dialect/NVGPU/optimize-shared-memory.mlir
index a7d0e5d30e3dd6..2f39753702b2b9 100644
--- a/mlir/test/Dialect/NVGPU/optimize-shared-memory.mlir
+++ b/mlir/test/Dialect/NVGPU/optimize-shared-memory.mlir
@@ -241,6 +241,35 @@ func.func @abort_if_subview(%arg0: memref<128x128xf16>,
// -----
+// A row of 96 f16 elements is 12 vectors of 128 bits, which is not a power of
+// 2. The XOR swizzle would move accesses into the next row; don't swizzle.
+
+// CHECK: @no_swizzle_non_power_of_2_row_f16([[arg0:%.+]]: memref<{{.*}}>, [[ldRow:%.+]]: index, [[ldCol:%.+]]: index, [[stRow:%.+]]: index, [[stCol:%.+]]: index, [[fragRow:%.+]]: index, [[fragCol:%.+]]: index)
+func.func @no_swizzle_non_power_of_2_row_f16(%arg0: memref<128x128xf16>,
+ %ldRow: index, %ldCol: index,
+ %stRow: index, %stCol: index,
+ %fragRow: index, %fragCol :index)
+ -> vector<4x2xf16> {
+ // CHECK: [[shm:%.+]] = memref.alloc
+ %shm = memref.alloc() : memref<64x96xf16, 3>
+
+ // CHECK-NOT: arith.xori
+ // CHECK: nvgpu.device_async_copy [[arg0]][[[ldRow]], [[ldCol]]], [[shm]][[[stRow]], [[stCol]]]
+ %0 = nvgpu.device_async_copy %arg0[%ldRow, %ldCol], %shm[%stRow, %stCol], 8
+ : memref<128x128xf16> to memref<64x96xf16, 3>
+ %1 = nvgpu.device_async_create_group %0
+ nvgpu.device_async_wait %1 numGroups = 1
+
+ // CHECK-NOT: arith.xori
+ // CHECK: nvgpu.ldmatrix [[shm]][[[fragRow]], [[fragCol]]]
+ %mat = nvgpu.ldmatrix %shm[%fragRow, %fragCol] numTiles = 4 transpose = false
+ : memref<64x96xf16, 3> -> vector<4x2xf16>
+
+ return %mat: vector<4x2xf16>
+}
+
+// -----
+
// Ensure this case not crash
// CHECK-LABEL: func @test_0_d
>From a9d0e740c4d9f573bf86166d576632ca903adcf2 Mon Sep 17 00:00:00 2001
From: aditya dandwate <adandwate at polymagelabs.com>
Date: Fri, 30 Oct 2026 16:51:11 +0530
Subject: [PATCH 2/2] [mlir][nvgpu] Expose which memrefs optimize-shared-memory
can swizzle (NFC)
Move the checks on a memref's type out of
`optimizeSharedMemoryReadsAndWrites` into a new public function,
`canSwizzleSharedMemoryAccesses`. A pipeline that pads the shared memory
buffers that won't be swizzled can then ask for that decision instead of
duplicating it.
Assisted-by: Claude
---
.../Dialect/NVGPU/Transforms/Transforms.h | 10 +++++
.../NVGPU/Transforms/OptimizeSharedMemory.cpp | 42 ++++++++++---------
2 files changed, 33 insertions(+), 19 deletions(-)
diff --git a/mlir/include/mlir/Dialect/NVGPU/Transforms/Transforms.h b/mlir/include/mlir/Dialect/NVGPU/Transforms/Transforms.h
index 2ff9ac43851d84..c2beab4a1fed1b 100644
--- a/mlir/include/mlir/Dialect/NVGPU/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/NVGPU/Transforms/Transforms.h
@@ -16,6 +16,7 @@
#include "mlir/IR/Operation.h"
namespace mlir {
+class MemRefType;
class RewriterBase;
namespace nvgpu {
@@ -24,6 +25,15 @@ namespace nvgpu {
/// Passes
///
+/// Returns true if `optimizeSharedMemoryReadsAndWrites` can swizzle accesses to
+/// shared memory buffers of type `memRefType`. Their rows, i.e., the innermost
+/// dimension, must be of static size, longer than 16 bytes, and span a power of
+/// 2 number of bits. The swizzle XORs the index of each 128-bit vector in a row
+/// with bits of the row index; it keeps the result within the row only if a row
+/// is a power of 2 number of vectors. Memrefs with vector element types are not
+/// supported.
+bool canSwizzleSharedMemoryAccesses(MemRefType memRefType);
+
/// Optimizes vectorized accesses to a shared memory buffer specified by
/// memrefValue. This transformation assumes the following:
/// 1) All relevant accesses to `memrefValue` are contained with `parentOp`.
diff --git a/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp b/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
index ac3102d4b1815b..f82db90c38efff 100644
--- a/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
+++ b/mlir/lib/Dialect/NVGPU/Transforms/OptimizeSharedMemory.cpp
@@ -145,34 +145,21 @@ getShmReadAndWriteOps(Operation *parentOp, Value shmMemRef,
return success();
}
-llvm::LogicalResult
-mlir::nvgpu::optimizeSharedMemoryReadsAndWrites(Operation *parentOp,
- Value memrefValue) {
- auto memRefType = dyn_cast<MemRefType>(memrefValue.getType());
- if (!memRefType || !NVGPUDialect::hasSharedMemoryAddressSpace(memRefType))
- return failure();
-
+bool mlir::nvgpu::canSwizzleSharedMemoryAccesses(MemRefType memRefType) {
// Not support 0D MemRefs.
if (memRefType.getRank() == 0)
- return failure();
+ return false;
// Only support memrefs with scalar element types (i.e., int or float).
// Memrefs with vector element types are not supported.
if (!memRefType.getElementType().isIntOrFloat())
- return failure();
-
- // Abort if the given value has any sub-views; we do not do any alias
- // analysis.
- bool hasSubView = false;
- parentOp->walk([&](memref::SubViewOp subView) { hasSubView = true; });
- if (hasSubView)
- return failure();
+ return false;
// Check if this is necessary given the assumption of 128b accesses:
// If dim[rank-1] is small enough to fit 8 rows in a 128B line.
const int64_t rowSize = memRefType.getDimSize(memRefType.getRank() - 1);
if (ShapedType::isDynamic(rowSize) || rowSize == 0)
- return failure();
+ return false;
const int64_t rowsPerLine =
(8 * kSharedMemoryLineSizeBytes / memRefType.getElementTypeBitWidth()) /
@@ -180,12 +167,29 @@ mlir::nvgpu::optimizeSharedMemoryReadsAndWrites(Operation *parentOp,
const int64_t threadGroupSize =
1LL << (7 - llvm::Log2_64(kDefaultVectorSizeBits / 8));
if (rowsPerLine >= threadGroupSize)
- return failure();
+ return false;
// The XOR swizzle stays within a row only if the row size is a power of 2.
// Otherwise it moves some accesses into the next row, where they overwrite
// other elements.
- if (!llvm::isPowerOf2_64(rowSize))
+ return llvm::isPowerOf2_64(rowSize);
+}
+
+llvm::LogicalResult
+mlir::nvgpu::optimizeSharedMemoryReadsAndWrites(Operation *parentOp,
+ Value memrefValue) {
+ auto memRefType = dyn_cast<MemRefType>(memrefValue.getType());
+ if (!memRefType || !NVGPUDialect::hasSharedMemoryAddressSpace(memRefType))
+ return failure();
+
+ if (!canSwizzleSharedMemoryAccesses(memRefType))
+ return failure();
+
+ // Abort if the given value has any sub-views; we do not do any alias
+ // analysis.
+ bool hasSubView = false;
+ parentOp->walk([&](memref::SubViewOp subView) { hasSubView = true; });
+ if (hasSubView)
return failure();
// Get sets of operations within the function that read/write to shared
More information about the Mlir-commits
mailing list