[Mlir-commits] [mlir] [MLIR][XeGPU] Fix layout recovery for dead loop-carried values (PR #205884)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Jun 25 11:49:43 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-gpu
Author: Nishant Patel (nbpatel)
<details>
<summary>Changes</summary>
A single backward scan resolves a loop-carried value only when it has a downstream consumer; with no consumer, its layout is known solely from block-arg uses seen later in the scan and gets missed. Repeat the scan to stamp the layout.
---
Full diff: https://github.com/llvm/llvm-project/pull/205884.diff
2 Files Affected:
- (modified) mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp (+29-6)
- (modified) mlir/test/Dialect/XeGPU/xegpu-recover-layout.mlir (+45)
``````````diff
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index 2a13997aa181f..674646eba8a6d 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -319,12 +319,35 @@ bool xegpu::recoverTemporaryLayouts(Operation *rootOp) {
});
};
removeTemporaryLayoutAttrs(rootOp);
- rootOp->walk([&](func::FuncOp func) {
- processFunc(func.getBody(), func.getSymName());
- });
- rootOp->walk([&](gpu::GPUFuncOp func) {
- processFunc(func.getBody(), func.getName());
- });
+
+ // Count layout attributes attached under `rootOp`. Recovery only adds
+ // attributes, so this count is used to detect a fixed point.
+ auto countTemporaryLayouts = [&]() {
+ unsigned count = 0;
+ rootOp->walk([&](Operation *nestOp) {
+ for (const NamedAttribute &namedAttr : nestOp->getDiscardableAttrs())
+ if (isa<xegpu::DistributeLayoutAttr>(namedAttr.getValue()))
+ ++count;
+ });
+ return count;
+ };
+
+ // A single backward sweep cannot resolve loop-carried values whose layout is
+ // only known from block-argument uses that are themselves annotated later in
+ // the sweep. Repeat until the layout count stops changing; recovery only adds
+ // attributes, so this terminates.
+ unsigned prevCount = 0;
+ unsigned curCount = countTemporaryLayouts();
+ do {
+ prevCount = curCount;
+ rootOp->walk([&](func::FuncOp func) {
+ processFunc(func.getBody(), func.getSymName());
+ });
+ rootOp->walk([&](gpu::GPUFuncOp func) {
+ processFunc(func.getBody(), func.getName());
+ });
+ curCount = countTemporaryLayouts();
+ } while (curCount != prevCount);
return true;
}
diff --git a/mlir/test/Dialect/XeGPU/xegpu-recover-layout.mlir b/mlir/test/Dialect/XeGPU/xegpu-recover-layout.mlir
index e2a4897fac519..9533037273d49 100644
--- a/mlir/test/Dialect/XeGPU/xegpu-recover-layout.mlir
+++ b/mlir/test/Dialect/XeGPU/xegpu-recover-layout.mlir
@@ -146,3 +146,48 @@ gpu.func @if_basic(
gpu.return
}
}
+
+// -----
+// Test scf.for with a dead loop-carried result: %bias is used inside the loop
+// but the loop result %2#1 is unused. The dead chain (arith.negf -> scf.yield
+// -> %2#1) only gets its layout from the loop block-arg uses, so recovery must
+// iterate to a fixed point to fill it in.
+
+gpu.module @test_dead_carry {
+// CHECK-LABEL: gpu.func @for_dead_carry
+gpu.func @for_dead_carry(%arg0: memref<8x16xf16>, %arg1: memref<16x16xf16>, %arg2: memref<8x16xf32>) {
+ %c0 = arith.constant 0 : index
+ %c128 = arith.constant 128 : index
+ %c16 = arith.constant 16 : index
+ %0 = xegpu.create_nd_tdesc %arg0 : memref<8x16xf16> -> !xegpu.tensor_desc<8x16xf16>
+ %1 = xegpu.create_nd_tdesc %arg1 : memref<16x16xf16> -> !xegpu.tensor_desc<16x16xf16>
+ %cst = arith.constant dense<0.000000e+00> : vector<8x16xf32>
+ %cst_0 = arith.constant dense<1.000000e+00> : vector<8x16xf32>
+ // CHECK: scf.for
+ %2:2 = scf.for %arg3 = %c0 to %c128 step %c16
+ iter_args(%acc = %cst, %bias = %cst_0) -> (vector<8x16xf32>, vector<8x16xf32>) {
+ %4 = xegpu.load_nd %0[%c0, %c0] {layout = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
+ : !xegpu.tensor_desc<8x16xf16> -> vector<8x16xf16>
+ %5 = xegpu.load_nd %1[%c0, %c0] {layout = #xegpu.layout<lane_layout = [1, 16], lane_data = [2, 1]>}
+ : !xegpu.tensor_desc<16x16xf16> -> vector<16x16xf16>
+ %6 = xegpu.dpas %4, %5, %acc
+ {layout_a = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>,
+ layout_b = #xegpu.layout<lane_layout = [1, 16], lane_data = [2, 1]>,
+ layout_cd = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
+ : vector<8x16xf16>, vector<16x16xf16>, vector<8x16xf32> -> vector<8x16xf32>
+ %7 = arith.addf %6, %bias : vector<8x16xf32>
+ // CHECK: arith.negf
+ // CHECK-SAME: layout_operand_0 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>
+ // CHECK-SAME: layout_result_0 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>
+ %8 = arith.negf %bias : vector<8x16xf32>
+ // CHECK: scf.yield
+ // CHECK-SAME: layout_operand_0 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>
+ // CHECK-SAME: layout_operand_1 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>
+ scf.yield %7, %8 : vector<8x16xf32>, vector<8x16xf32>
+ }
+ %3 = xegpu.create_nd_tdesc %arg2 : memref<8x16xf32> -> !xegpu.tensor_desc<8x16xf32>
+ xegpu.store_nd %2#0, %3[%c0, %c0] {layout = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1]>}
+ : vector<8x16xf32>, !xegpu.tensor_desc<8x16xf32>
+ gpu.return
+}
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/205884
More information about the Mlir-commits
mailing list