blob: 6a625ff515029f49240235f0c9a9e66f591beada [file] [edit]
// RUN: mlir-opt --test-xegpu-resolve-layout-conflicts -allow-unregistered-dialect -split-input-file %s | FileCheck %s
#inst_data_8x16 = #xegpu.layout<inst_data = [8, 16]>
#inst_data_16x16 = #xegpu.layout<inst_data = [16, 16]>
#inst_data_32x16 = #xegpu.layout<inst_data = [32, 16]>
#inst_data_16 = #xegpu.layout<inst_data = [16]>
#inst_data_32 = #xegpu.layout<inst_data = [32]>
#inst_data_1x2x16 = #xegpu.layout<inst_data = [1, 2, 16]>
#inst_data_1x32 = #xegpu.layout<inst_data = [1, 32]>
gpu.module @test {
// CHECK-LABEL: func.func @load_nd_with_conflicting_tensor_desc
// CHECK: %{{.*}} = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>
// CHECK-NEXT: %[[T1:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [8, 16]>>
// CHECK-NEXT: %{{.*}} = xegpu.load_nd %[[T1]][%{{.*}}, %{{.*}}] <{layout = #xegpu.layout<inst_data = [8, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [8, 16]>> -> vector<16x16xf16>
func.func @load_nd_with_conflicting_tensor_desc(%arg0: memref<64x64xf16>) {
%c0 = arith.constant 0 : index
%0 = xegpu.create_nd_tdesc %arg0 : memref<64x64xf16>
-> !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
%1 = xegpu.load_nd %0 [%c0, %c0] {layout = #inst_data_8x16} : !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
-> vector<16x16xf16>
xegpu.prefetch_nd %0 [%c0, %c0] {layout = #inst_data_16x16} : !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
return
}
// CHECK-LABEL: func.func @multiple_tensor_desc_conflicts
// CHECK: %[[C0:.*]] = arith.constant 0 : index
// CHECK-NEXT: %[[T0:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [8, 16]>>
// CHECK-NEXT: %[[T1:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [16, 16]>>
// CHECK-NEXT: %[[T2:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [32, 16]>>
// CHECK-NEXT: %{{.*}} = xegpu.load_nd %[[T0]][%[[C0]], %[[C0]]] <{layout = #xegpu.layout<inst_data = [8, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [8, 16]>> -> vector<32x16xf16>
// CHECK-NEXT: %{{.*}} = xegpu.load_nd %[[T2]][%[[C0]], %[[C0]]] <{layout = #xegpu.layout<inst_data = [32, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [32, 16]>> -> vector<32x16xf16>
// CHECK-NEXT: xegpu.prefetch_nd %[[T1]][%[[C0]], %[[C0]]] <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<32x16xf16, #xegpu.layout<inst_data = [16, 16]>>
func.func @multiple_tensor_desc_conflicts(%arg0: memref<64x64xf16>) {
%c0 = arith.constant 0 : index
%tdesc1 = xegpu.create_nd_tdesc %arg0 : memref<64x64xf16>
-> !xegpu.tensor_desc<32x16xf16, #inst_data_8x16>
%load1 = xegpu.load_nd %tdesc1 [%c0, %c0] {layout = #inst_data_8x16} : !xegpu.tensor_desc<32x16xf16, #inst_data_8x16>
-> vector<32x16xf16>
%load2 = xegpu.load_nd %tdesc1 [%c0, %c0] {layout = #inst_data_32x16} : !xegpu.tensor_desc<32x16xf16, #inst_data_8x16>
-> vector<32x16xf16>
xegpu.prefetch_nd %tdesc1 [%c0, %c0] {layout = #inst_data_16x16} : !xegpu.tensor_desc<32x16xf16, #inst_data_8x16>
return
}
// CHECK-LABEL: func.func @load_nd_with_conflicting_tensor_desc_in_loop
// CHECK: %[[T0:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>
// CHECK-NEXT: %[[T1:.*]] = xegpu.create_nd_tdesc %{{.*}} : memref<64x64xf16>
// CHECK-SAME: -> !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [8, 16]>>
// CHECK-NEXT: %{{.*}}:2 = scf.for %{{.*}} = %{{.*}} iter_args(%{{.*}} = %{{.*}}, %{{.*}} = %[[T0]])
// CHECK-SAME: -> (vector<16x16xf16>, !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>) {
// CHECK-NEXT: %{{.*}} = xegpu.load_nd %[[T1]][%{{.*}}] <{layout = #xegpu.layout<inst_data = [8, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [8, 16]>> -> vector<16x16xf16>
// CHECK: scf.yield %{{.*}}, %{{.*}} : vector<16x16xf16>, !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>
// CHECK: xegpu.prefetch_nd %[[T0]][%{{.*}}] <{layout = #xegpu.layout<inst_data = [16, 16]>}> :
// CHECK-SAME: !xegpu.tensor_desc<16x16xf16, #xegpu.layout<inst_data = [16, 16]>>
// CHECK-NEXT: return
func.func @load_nd_with_conflicting_tensor_desc_in_loop(%arg0: memref<64x64xf16>) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c4 = arith.constant 4 : index
%cst = arith.constant {layout_result_0 = #inst_data_8x16} dense<0.0> : vector<16x16xf16>
%0 = xegpu.create_nd_tdesc %arg0 : memref<64x64xf16>
-> !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
%1:2 = scf.for %i = %c0 to %c4 step %c1 iter_args(%acc = %cst, %tdesc = %0)
-> (vector<16x16xf16>, !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>) {
%2 = xegpu.load_nd %tdesc [%c0, %c0] {layout = #inst_data_8x16} : !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
-> vector<16x16xf16>
%3 = arith.addf %acc, %2 {layout_result_0 = #inst_data_8x16} : vector<16x16xf16>
scf.yield %3, %tdesc : vector<16x16xf16>, !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
} {layout_result_0 = #inst_data_8x16}
xegpu.prefetch_nd %0 [%c0, %c0] {layout = #inst_data_16x16} : !xegpu.tensor_desc<16x16xf16, #inst_data_16x16>
return
}
// CHECK-LABEL: func.func @elementwise_conflict
// CHECK-DAG: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : () -> vector<32x32xf16>
// CHECK-DAG: %[[V1:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [32, 16]>} : () -> vector<32x32xf16>
// CHECK-DAG: %[[CVT:.*]] = xegpu.convert_layout %[[V1]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [32, 16]>, target_layout = #xegpu.layout<inst_data = [8, 16]>}>
// CHECK-SAME: : vector<32x32xf16>
// CHECK: %[[ADD:.*]] = arith.addf %[[V0]], %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : vector<32x32xf16>
// CHECK: return
func.func @elementwise_conflict() {
%0 = "some_op"() {layout_result_0 = #inst_data_8x16} : () -> vector<32x32xf16>
%1 = "some_op"() {layout_result_0 = #inst_data_32x16} : () -> vector<32x32xf16>
%2 = arith.addf %0, %1 {layout_result_0 = #inst_data_8x16} : vector<32x32xf16>
return
}
// CHECK-LABEL: func.func @elementwise_conflict_uniform
// CHECK-DAG: %[[V0:.*]] = "some_op"() : () -> vector<2xf16>
// CHECK-DAG: %[[V1:.*]] = "some_op"() : () -> vector<2xf16>
// CHECK: %[[ADD:.*]] = arith.addf %[[V0]], %[[V1]] : vector<2xf16>
// CHECK: return
func.func @elementwise_conflict_uniform() {
%0 = "some_op"() : () -> vector<2xf16>
%1 = "some_op"() : () -> vector<2xf16>
%non_tensor_data_vec = arith.addf %0, %1 : vector<2xf16>
return
}
// CHECK-LABEL: func.func @broadcast_source_conflict
// CHECK: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [16]>} : () -> vector<16xf16>
// CHECK: %[[CVT:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [16]>, target_layout = #xegpu.slice<#xegpu.layout<inst_data = [16, 16]>, dims = [0]>}>
// CHECK-SAME: : vector<16xf16>
// CHECK: %[[BC:.*]] = vector.broadcast %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>} : vector<16xf16> to vector<16x16xf16>
// CHECK: return
func.func @broadcast_source_conflict() {
%0 = "some_op"() {layout_result_0 = #inst_data_16} : () -> vector<16xf16>
%1 = vector.broadcast %0 {layout_result_0 = #inst_data_16x16} : vector<16xf16> to vector<16x16xf16>
return
}
// CHECK-LABEL: func.func @shapecast_source_conflict
// CHECK: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [1, 2, 16]>} : () -> vector<2x4x32xf16>
// CHECK: %[[CVT:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [1, 2, 16]>, target_layout = #xegpu.layout<inst_data = [1, 1, 32]>}>
// CHECK-SAME: : vector<2x4x32xf16>
// CHECK: %[[SC:.*]] = vector.shape_cast %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [1, 32]>} : vector<2x4x32xf16> to vector<1x256xf16>
// CHECK: return
func.func @shapecast_source_conflict() {
%0 = "some_op"() {layout_result_0 = #inst_data_1x2x16} : () -> vector<2x4x32xf16>
%1 = vector.shape_cast %0 {layout_result_0 = #inst_data_1x32} : vector<2x4x32xf16> to vector<1x256xf16>
return
}
// CHECK-LABEL: func.func @bitcast_source_conflict
// CHECK: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [1, 16]>} : () -> vector<32x16xf32>
// CHECK: %[[CVT:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [1, 16]>, target_layout = #xegpu.layout<inst_data = [16, 16]>}>
// CHECK-SAME: : vector<32x16xf32>
// CHECK: %[[BC:.*]] = vector.bitcast %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [16, 32]>} : vector<32x16xf32> to vector<32x32xf16>
// CHECK: return
func.func @bitcast_source_conflict() {
%0 = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [1, 16]>} : () -> vector<32x16xf32>
%1 = vector.bitcast %0 {layout_result_0 = #xegpu.layout<inst_data = [16, 32]>} : vector<32x16xf32> to vector<32x32xf16>
return
}
// The accumulator `arith.constant` is trivially rematerializable, so the
// conflict resolver clones it with the consumer's expected layout instead
// of inserting an `xegpu.convert_layout`
// CHECK-LABEL: func.func @multireduction_source_conflict
// CHECK-DAG: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [32, 16]>} : () -> vector<32x32xf16>
// CHECK-DAG: %[[CVT0:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [32, 16]>, target_layout = #xegpu.layout<inst_data = [16, 16]>}>
// CHECK-SAME: : vector<32x32xf16>
// CHECK-DAG: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [16, 16]>, dims = [0]>}
// CHECK-SAME: dense<0.000000e+00> : vector<32xf16>
// CHECK-NOT: xegpu.convert_layout %{{.*}} : vector<32xf16>
// CHECK: %[[MR:.*]] = vector.multi_reduction <add>, %[[CVT0]], %[[CST]]
// CHECK-SAME: {layout_result_0 = #xegpu.slice<#xegpu.layout<inst_data = [16, 16]>, dims = [0]>}
// CHECK-SAME: [0] : vector<32x32xf16> to vector<32xf16>
// CHECK: return
func.func @multireduction_source_conflict() {
%0 = "some_op"() {layout_result_0 = #inst_data_32x16} : () -> vector<32x32xf16>
%acc = arith.constant {layout_result_0 = #inst_data_32} dense<0.0> : vector<32xf16>
%1 = vector.multi_reduction <add>, %0, %acc
{layout_result_0 = #xegpu.slice<#inst_data_16x16, dims = [0]>}
[0] : vector<32x32xf16> to vector<32xf16>
return
}
// CHECK-LABEL: func.func @insert_strided_slice_source_conflict
// CHECK-DAG: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [1, 16]>} : () -> vector<16x16xf16>
// CHECK-DAG: %[[CVT:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [1, 16]>, target_layout = #xegpu.layout<inst_data = [16, 16]>}>
// CHECK-SAME: : vector<16x16xf16>
// CHECK-DAG: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [1, 16, 16]>}
// CHECK-SAME: dense<0.000000e+00> : vector<2x32x32xf16>
// CHECK: %[[ISS:.*]] = vector.insert_strided_slice %[[CVT]], %[[CST]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [1, 16, 16]>, offsets = [0, 0, 0], strides = [1, 1]}
// CHECK-SAME: : vector<16x16xf16> into vector<2x32x32xf16>
// CHECK: return
func.func @insert_strided_slice_source_conflict() {
%0 = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [1, 16]>} : () -> vector<16x16xf16>
%1 = arith.constant { layout_result_0 = #xegpu.layout<inst_data = [1, 16, 16]>}
dense<0.0> : vector<2x32x32xf16>
%2 = vector.insert_strided_slice %0, %1 {offsets = [0, 0, 0], strides = [1, 1],
layout_result_0 = #xegpu.layout<inst_data = [1, 16, 16]>} : vector<16x16xf16> into vector<2x32x32xf16>
return
}
// CHECK-LABEL: func.func @conflict_inside_loop
// CHECK-DAG: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>}
// CHECK-SAME: dense<0.000000e+00> : vector<16x16xf16>
// CHECK: %[[FOR:.*]] = scf.for %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%[[ACC:.*]] = %[[CST]]) -> (vector<16x16xf16>) {
// CHECK: %[[V1:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>} : () -> vector<16x16xf16>
// CHECK: %[[CVT:.*]] = xegpu.convert_layout %[[V1]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [16, 16]>, target_layout = #xegpu.layout<inst_data = [8, 16]>}>
// CHECK-SAME: : vector<16x16xf16>
// CHECK: %[[ADD:.*]] = arith.addf %[[ACC]], %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : vector<16x16xf16>
// CHECK: scf.yield %[[ADD]] : vector<16x16xf16>
// CHECK: }
// CHECK: return
func.func @conflict_inside_loop() {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c4 = arith.constant 4 : index
%cst = arith.constant {layout_result_0 = #inst_data_8x16} dense<0.0> : vector<16x16xf16>
%0 = scf.for %i = %c0 to %c4 step %c1 iter_args(%acc = %cst) -> vector<16x16xf16> {
%1 = "some_op"() {layout_result_0 = #inst_data_16x16} : () -> vector<16x16xf16>
%2 = arith.addf %acc, %1 {layout_result_0 = #inst_data_8x16} : vector<16x16xf16>
scf.yield %2 : vector<16x16xf16>
} {layout_result_0 = #inst_data_8x16}
return
}
// CHECK-LABEL: func.func @conflict_postop
// CHECK-DAG: %[[CST:.*]] = arith.constant {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>}
// CHECK-SAME: dense<0.000000e+00> : vector<16x16xf16>
// CHECK: %[[FOR:.*]] = scf.for %{{.*}} = %{{.*}} to %{{.*}} step %{{.*}} iter_args(%[[ACC:.*]] = %[[CST]]) -> (vector<16x16xf16>) {
// CHECK: %[[V1:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>} : () -> vector<16x16xf16>
// CHECK: %[[ADD:.*]] = arith.addf %[[ACC]], %[[V1]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [16, 16]>} : vector<16x16xf16>
// CHECK: scf.yield %[[ADD]] : vector<16x16xf16>
// CHECK: }
// CHECK: %[[CVT:.*]] = xegpu.convert_layout %[[FOR]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [16, 16]>, target_layout = #xegpu.layout<inst_data = [8, 16]>}>
// CHECK-SAME: : vector<16x16xf16>
// CHECK: %[[EXP:.*]] = math.exp %[[CVT]]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : vector<16x16xf16>
// CHECK: return
func.func @conflict_postop() {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
%c4 = arith.constant 4 : index
%cst = arith.constant {layout_result_0 = #inst_data_16x16} dense<0.0> : vector<16x16xf16>
%0 = scf.for %i = %c0 to %c4 step %c1 iter_args(%acc = %cst) -> vector<16x16xf16> {
%1 = "some_op"() {layout_result_0 = #inst_data_16x16} : () -> vector<16x16xf16>
%2 = arith.addf %acc, %1 {layout_result_0 = #inst_data_16x16} : vector<16x16xf16>
scf.yield %2 : vector<16x16xf16>
} {layout_result_0 = #inst_data_16x16}
%1 = math.exp %0 {layout_result_0 = #inst_data_8x16} : vector<16x16xf16>
return
}
// CHECK-LABEL: func.func @convert_layout
// CHECK: %[[V1:.*]] = xegpu.convert_layout %[[V0:.*]] <{input_layout = #xegpu.layout<sg_layout = [8, 4], sg_data = [4, 32]>, target_layout = #xegpu.layout<sg_layout = [4, 8], sg_data = [8, 16]>}> : vector<32x128xf32>
func.func @convert_layout() {
%src0 = arith.constant
{layout_result_0 = #xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>}
dense<0.000000e+00>
: vector<32x128xf32>
%src0_cvt = xegpu.convert_layout %src0
<{target_layout = #xegpu.layout<sg_layout=[8, 4], sg_data=[4, 32]>}>
: vector<32x128xf32>
%src1 = arith.constant
{layout_result_0 = #xegpu.layout<sg_layout = [4, 8], sg_data = [8, 16]>}
dense<1.000000e+00>
: vector<32x128xf32>
%dest = arith.addf %src0_cvt, %src1
{layout_result_0 = #xegpu.layout<sg_layout = [4, 8], sg_data = [8, 16]>}
: vector<32x128xf32>
%desc_cvt = xegpu.convert_layout %dest
<{target_layout = #xegpu.layout<sg_layout=[4, 8], sg_data=[8, 16]>}>
: vector<32x128xf32>
return
}
// A `vector.step` reaches two consumers that need different slice layouts of
// the anchor's sg_layout (one along dim 0, one along dim 1). The conflict is
// trivially rematerializable, so the resolver clones the step with the second
// layout instead of inserting an `xegpu.convert_layout` (which would lower to
// a cross-subgroup SLM round-trip at WG-to-SG distribution time).
// CHECK-LABEL: gpu.func @step_clone_via_anchor
// CHECK-DAG: vector.step {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
// CHECK-DAG: vector.step {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>, dims = [1]>} : vector<32xindex>
// CHECK-NOT: xegpu.convert_layout {{.*}} : vector<32xindex>
gpu.func @step_clone_via_anchor(%arg0: i64, %arg1: memref<32x32xf32>) kernel {
%mask = arith.constant {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} dense<true> : vector<32x32xi1>
%cst32 = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>, dims = [1]>} dense<32> : vector<32xindex>
%step = vector.step {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
%col = arith.muli %step, %cst32 {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>, dims = [1]>} : vector<32xindex>
%col2d = vector.shape_cast %col {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>} : vector<32xindex> to vector<32x1xindex>
%colb = vector.broadcast %col2d {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32x1xindex> to vector<32x32xindex>
%rowb = vector.broadcast %step {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32xindex> to vector<32x32xindex>
%off = arith.addi %colb, %rowb {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32x32xindex>
%v = xegpu.load %arg0[%off], %mask <{layout = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>}>
: i64, vector<32x32xindex>, vector<32x32xi1> -> vector<32x32xf32>
%tdesc = xegpu.create_nd_tdesc %arg1 : memref<32x32xf32> -> !xegpu.tensor_desc<32x32xf32>
xegpu.store_nd %v, %tdesc[0, 0] <{layout = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>}> : vector<32x32xf32>, !xegpu.tensor_desc<32x32xf32>
gpu.return
}
// Test that when a derived value (arith.muli) has a layout conflict on its
// uses, and the derived value's producer chain is trivially rematerializable
// (because step and constant stride are themselves trivially rematerializable),
// the resolver clones the arith.muli instead of inserting an xegpu.convert_layout.
// CHECK-LABEL: gpu.func @step_muli_clone_via_anchor
// CHECK-DAG: vector.step {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
// CHECK-DAG: arith.muli {{.*}} {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
// CHECK-DAG: arith.muli {{.*}} {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>, dims = [1]>} : vector<32xindex>
// CHECK-NOT: xegpu.convert_layout {{.*}} : vector<32xindex>
gpu.func @step_muli_clone_via_anchor(%arg0: i64, %arg1: memref<32x32xf32>) kernel {
%mask = arith.constant {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} dense<true> : vector<32x32xi1>
%cst32 = arith.constant {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} dense<32> : vector<32xindex>
%step = vector.step {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
%scaled = arith.muli %step, %cst32 {layout_result_0 = #xegpu.slice<#xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>, dims = [0]>} : vector<32xindex>
%col2d = vector.shape_cast %scaled {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 1]>} : vector<32xindex> to vector<32x1xindex>
%colb = vector.broadcast %col2d {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32x1xindex> to vector<32x32xindex>
%rowb = vector.broadcast %scaled {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32xindex> to vector<32x32xindex>
%off = arith.addi %colb, %rowb {layout_result_0 = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>} : vector<32x32xindex>
%v = xegpu.load %arg0[%off], %mask <{layout = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>}>
: i64, vector<32x32xindex>, vector<32x32xi1> -> vector<32x32xf32>
%tdesc = xegpu.create_nd_tdesc %arg1 : memref<32x32xf32> -> !xegpu.tensor_desc<32x32xf32>
xegpu.store_nd %v, %tdesc[0, 0] <{layout = #xegpu.layout<sg_layout = [32, 1], sg_data = [1, 32]>}> : vector<32x32xf32>, !xegpu.tensor_desc<32x32xf32>
gpu.return
}
// CHECK-LABEL: func.func @extract_source_conflict_with_order
// CHECK-DAG: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1], order = [2, 3, 0, 1]>} : () -> vector<2x4x16x32xf16>
// CHECK-DAG: %[[CVT:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1], order = [2, 3, 0, 1]>, target_layout = #xegpu.layout<lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1], order = [2, 3, 1, 0]>}>
// CHECK-SAME: : vector<2x4x16x32xf16>
// CHECK: %[[EXT:.*]] = vector.extract %[[CVT]][0, 0]
// CHECK-SAME: {layout_result_0 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1], order = [0, 1]>}
// CHECK-SAME: : vector<16x32xf16> from vector<2x4x16x32xf16>
// CHECK: return %[[EXT]] : vector<16x32xf16>
func.func @extract_source_conflict_with_order() -> vector<16x32xf16> {
%0 = "some_op"() {layout_result_0 = #xegpu.layout<lane_layout = [1, 1, 1, 16], lane_data = [1, 1, 1, 1], order = [2, 3, 0, 1]>} : () -> vector<2x4x16x32xf16>
%1 = vector.extract %0[0, 0] {layout_result_0 = #xegpu.layout<lane_layout = [1, 16], lane_data = [1, 1], order = [0, 1]>} : vector<16x32xf16> from vector<2x4x16x32xf16>
return %1 : vector<16x32xf16>
}
}
// -----
// Producer carries a layout that differs from the input_layout declared on a
// downstream xegpu.convert_layout consumer. ResolveLayoutConflicts must insert
// a bridging convert_layout between the producer and the existing
// convert_layout so the consumer's stated input_layout is honored.
// CHECK-LABEL: func.func @convert_layout_bridge_input_mismatch
// CHECK: %[[V0:.*]] = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : () -> vector<32x32xf16>
// CHECK-NEXT: %[[BRIDGE:.*]] = xegpu.convert_layout %[[V0]]
// CHECK-SAME: <{input_layout = #xegpu.layout<inst_data = [8, 16]>, target_layout = #xegpu.layout<inst_data = [32, 16]>}>
// CHECK-SAME: : vector<32x32xf16>
gpu.module @test_convert_layout_bridge {
func.func @convert_layout_bridge_input_mismatch() {
%0 = "some_op"() {layout_result_0 = #xegpu.layout<inst_data = [8, 16]>} : () -> vector<32x32xf16>
%1 = xegpu.convert_layout %0
<{input_layout = #xegpu.layout<inst_data = [16, 16]>,
target_layout = #xegpu.layout<inst_data = [32, 16]>}>
: vector<32x32xf16>
return
}
}