blob: f223b325c2f9edde30e416d40bb8445f058264e4 [file] [edit]
// RUN: mlir-opt -split-input-file \
// RUN: -test-xegpu-coalesce-gather-scatter="analyze-only=true" %s | FileCheck %s
// Contiguity analysis: stamps the `contiguity` attribute on gather/scatter ops
// whose offsets are contiguous (runs of >= 2) along the innermost dimension.
// The stamped value is the inner-dim contiguity, rounded down to a divisor of
// the inner extent. The analysis is target-independent and mask-independent;
// the lane_data split and any access-pattern gating live in the consumer.
// -----
// 1-D vector.step -> stride-1, fully contiguous over the 32-element inner dim.
// CHECK-LABEL: func.func @load_step_offsets(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
// CHECK-SAME: : i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
func.func @load_step_offsets(%ptr: i64) -> vector<32xf32> {
%offsets = vector.step : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// Dense stride-1 constant offsets -> contiguity 32.
// CHECK-LABEL: func.func @load_dense_ap_offsets(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_dense_ap_offsets(%ptr: i64) -> vector<32xi32> {
%offsets = arith.constant dense<[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
]> : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xi32>
return %v : vector<32xi32>
}
// -----
// Stride-4 offsets: not contiguous, no attribute stamped.
// CHECK-LABEL: func.func @load_stride4_no_attr(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
// CHECK-SAME: : i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
func.func @load_stride4_no_attr(%ptr: i64) -> vector<32xf32> {
%c4 = arith.constant 4 : index
%step = vector.step : vector<32xindex>
%splat = vector.broadcast %c4 : index to vector<32xindex>
%offsets = arith.muli %step, %splat : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// All-equal offsets: inner dim is constant, not contiguous -> no attribute.
// CHECK-LABEL: func.func @load_broadcast_offsets_no_attr(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
func.func @load_broadcast_offsets_no_attr(%ptr: i64) -> vector<32xf32> {
%offsets = arith.constant dense<0> : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// The analysis is mask-independent: a partial mask still gets the attribute
// (the mask check is a consumer concern).
// CHECK-LABEL: func.func @load_partial_mask(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_partial_mask(%ptr: i64, %mask: vector<32xi1>) -> vector<32xf32> {
%offsets = vector.step : vector<32xindex>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// Store with vector.step offsets -> contiguity 32 on the store.
// CHECK-LABEL: func.func @store_step_offsets(
// CHECK: xegpu.store
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @store_step_offsets(%ptr: i64, %v: vector<32xf32>) {
%offsets = vector.step : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
xegpu.store %v, %ptr[%offsets], %mask
: vector<32xf32>, i64, vector<32xindex>, vector<32xi1>
return
}
// -----
// 2-D leading-unit dim: contiguity measured on the inner dim -> 32.
// CHECK-LABEL: func.func @load_2d_leading_unit(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_2d_leading_unit(%ptr: i64) -> vector<1x32xf32> {
%step = vector.step : vector<32xindex>
%offsets = vector.shape_cast %step : vector<32xindex> to vector<1x32xindex>
%mask = arith.constant dense<true> : vector<1x32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<1x32xindex>, vector<1x32xi1> -> vector<1x32xf32>
return %v : vector<1x32xf32>
}
// -----
// True 2-D dense AP: each row stride-1 over 16 -> contiguity 16.
// CHECK-LABEL: func.func @load_2d_dense_ap(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 16 : i64}>
func.func @load_2d_dense_ap(%ptr: i64) -> vector<2x16xf32> {
%offsets = arith.constant dense<[
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
]> : vector<2x16xindex>
%mask = arith.constant dense<true> : vector<2x16xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<2x16xindex>, vector<2x16xi1> -> vector<2x16xf32>
return %v : vector<2x16xf32>
}
// -----
// 2-D dense values whose inner row is not a stride-1 AP: no attribute.
// CHECK-LABEL: func.func @load_2d_non_ap(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
func.func @load_2d_non_ap(%ptr: i64) -> vector<2x16xf32> {
%offsets = arith.constant dense<[
[0, 1, 2, 3, 4, 5, 6, 7, 100, 9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
]> : vector<2x16xindex>
%mask = arith.constant dense<true> : vector<2x16xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<2x16xindex>, vector<2x16xi1> -> vector<2x16xf32>
return %v : vector<2x16xf32>
}
// -----
// `divui` by a constant equal to the inner stride recovers stride-1
// contiguity: (step * 2) / 2 = [0,1,..,31] -> contiguity 32. The dividend is
// derived from vector.step (not a constant) so the divui is genuinely
// exercised even if the solver later folds all-constant arith ops.
// CHECK-LABEL: func.func @load_divui_recovers(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_divui_recovers(%ptr: i64) -> vector<32xf32> {
%step = vector.step : vector<32xindex>
%c2 = arith.constant dense<2> : vector<32xindex>
%even = arith.muli %step, %c2 : vector<32xindex>
%offsets = arith.divui %even, %c2 : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// `divui` by a constant that does not divide the inner stride: not recovered.
// CHECK-LABEL: func.func @load_divui_non_divisor(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
func.func @load_divui_non_divisor(%ptr: i64) -> vector<16xf32> {
%step = vector.step : vector<16xindex>
%c2 = arith.constant dense<2> : vector<16xindex>
%even = arith.muli %step, %c2 : vector<16xindex>
%c3 = arith.constant dense<3> : vector<16xindex>
%offsets = arith.divui %even, %c3 : vector<16xindex>
%mask = arith.constant dense<true> : vector<16xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<16xindex>, vector<16xi1> -> vector<16xf32>
return %v : vector<16xf32>
}
// -----
// `remui` collapsing each element to a constant residue: inner dim is uniform,
// not contiguous -> no attribute.
// CHECK-LABEL: func.func @load_remui_inner_uniform(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
func.func @load_remui_inner_uniform(%ptr: i64) -> vector<16xf32> {
%step = vector.step : vector<16xindex>
%c2 = arith.constant dense<2> : vector<16xindex>
%even = arith.muli %step, %c2 : vector<16xindex>
%offsets = arith.remui %even, %c2 : vector<16xindex>
%mask = arith.constant dense<true> : vector<16xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<16xindex>, vector<16xi1> -> vector<16xf32>
return %v : vector<16xf32>
}
// -----
// `shli` then `shrui` cancel: (step << 1) >> 1 -> stride 1 -> contiguity 32.
// CHECK-LABEL: func.func @load_shli_then_shrui(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_shli_then_shrui(%ptr: i64) -> vector<32xf32> {
%step = vector.step : vector<32xindex>
%k = arith.constant dense<1> : vector<32xindex>
%doubled = arith.shli %step, %k : vector<32xindex>
%offsets = arith.shrui %doubled, %k : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// `shli` alone scales the stride to 2: not contiguous -> no attribute.
// CHECK-LABEL: func.func @load_shli_unchanged(
// CHECK: xegpu.load
// CHECK-NOT: contiguity
func.func @load_shli_unchanged(%ptr: i64) -> vector<32xf32> {
%step = vector.step : vector<32xindex>
%k = arith.constant dense<1> : vector<32xindex>
%offsets = arith.shli %step, %k : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// `select` between two AP arms with matching inner-dim properties preserves
// contiguity 32.
// CHECK-LABEL: func.func @load_select_two_aps(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 32 : i64}>
func.func @load_select_two_aps(%ptr: i64, %cond: i1) -> vector<32xf32> {
%step = vector.step : vector<32xindex>
%a = arith.constant dense<0> : vector<32xindex>
%b = arith.constant dense<64> : vector<32xindex>
%baseA = arith.addi %step, %a : vector<32xindex>
%baseB = arith.addi %step, %b : vector<32xindex>
%offsets = arith.select %cond, %baseA, %baseB : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}
// -----
// A pre-existing `contiguity` takes precedence: the analysis leaves it alone.
// CHECK-LABEL: func.func @user_attr_preserved(
// CHECK: xegpu.load
// CHECK-SAME: <{contiguity = 2 : i64}>
func.func @user_attr_preserved(%ptr: i64) -> vector<32xf32> {
%offsets = vector.step : vector<32xindex>
%mask = arith.constant dense<true> : vector<32xi1>
%v = xegpu.load %ptr[%offsets], %mask <{contiguity = 2 : i64}>
: i64, vector<32xindex>, vector<32xi1> -> vector<32xf32>
return %v : vector<32xf32>
}