[DT][CPU]: scalable tile size selection for RVV (#24601)

Enables scalable tile size selection and propagation for RVV - as it is
already done for SVE.

In the SVE tests, there are more edge-cases with scalable tile sizes and
the behavior here is analogous to what we expect in RVV.

Signed-off-by: Ege Beysel <beyselege@gmail.com>
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp b/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
index 600ade9..c590d8f 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/KernelDispatch.cpp
@@ -1771,11 +1771,12 @@
     LDBG() << "SME is not supported yet!";
     return false;
   }
-  if (hasAnySVEFeature(targetConfig) && ShapedType::isDynamic(n0)) {
+  if ((hasAnySVEFeature(targetConfig) || hasAnyVFeature(targetConfig)) &&
+      ShapedType::isDynamic(n0)) {
     // Set the corresponding scalable tile size and flag for the inner N
-    // dimension, i.e. N0 from the iteration domain ([b,] M1, N1, K1, M0, N0,
-    // K0). The inner M dimension is not considered here, because SVE currently
-    // only makes the N dimension scalable.
+    // dimension, i.e. n1 from the iteration domain ([b, ], m0, n0, k0, m1, n1,
+    // k1). The inner M dimension is not considered here, because SVE/RVV
+    // currently only make the N dimension scalable.
     vecTileSizes[mmt4dDimBase + 4] =
         scalableInnerTilesAndFlags.value().first[1];
     vecScalableTileFlags[mmt4dDimBase + 4] =
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/BUILD.bazel b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/BUILD.bazel
index d39c820..7bbe656 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/BUILD.bazel
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/BUILD.bazel
@@ -60,6 +60,7 @@
             "select_aarch64_sve_lowering_strategy.mlir",
             "select_aarch64_sve_lowering_strategy_peeling.mlir",
             "select_riscv_lowering_strategy.mlir",
+            "select_riscv_v_lowering_strategy.mlir",
             "select_x86_64_lowering_strategy.mlir",
             "split_reduction.mlir",
             "synchronize_symbol_visibility.mlir",
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/CMakeLists.txt
index abc526f..25afeb8 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/CMakeLists.txt
@@ -55,6 +55,7 @@
     "select_aarch64_sve_lowering_strategy.mlir"
     "select_aarch64_sve_lowering_strategy_peeling.mlir"
     "select_riscv_lowering_strategy.mlir"
+    "select_riscv_v_lowering_strategy.mlir"
     "select_x86_64_lowering_strategy.mlir"
     "split_reduction.mlir"
     "synchronize_symbol_visibility.mlir"
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_riscv_v_lowering_strategy.mlir b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_riscv_v_lowering_strategy.mlir
new file mode 100644
index 0000000..0d5af15
--- /dev/null
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/select_riscv_v_lowering_strategy.mlir
@@ -0,0 +1,195 @@
+// Tests for RISC-V RVV targets with scalable vectorization enabled.
+// These tests verify that the lowering strategy correctly handles
+// mmt4d, pack, and unpack ops with scalable inner tile sizes.
+//
+// RUN: iree-opt --pass-pipeline='builtin.module(iree-llvmcpu-select-lowering-strategy)' \
+// RUN:   --iree-llvmcpu-enable-scalable-vectorization=true --split-input-file %s | FileCheck %s
+
+// This case tests if the inner tile size of the mmt4d is inferred properly
+// from the shape-aware HAL binding and set accordingly.
+
+#pipeline_layout = #hal.pipeline.layout<constants = 0, bindings = [
+  #hal.pipeline.binding<storage_buffer>
+]>
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map = affine_map<()[s0] -> (256 ceildiv s0)>
+func.func @mmt4d_tensors(%arg0: tensor<32x128x7x1xf32>, %arg1 : tensor<?x128x?x1xf32>) -> tensor<32x?x7x?xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %c8 = arith.constant 8 : index
+  %c0 = arith.constant 0 : index
+  %vscale = vector.vscale
+  %c8_vscale = arith.muli %vscale, %c8 : index
+  %n0 = affine.apply #map()[%c8_vscale]
+  %0 = hal.interface.binding.subspan layout(#pipeline_layout) binding(0) alignment(64) offset(%c0) : !iree_tensor_ext.dispatch.tensor<readwrite:tensor<32x?x7x?xf32>>{%n0, %c8_vscale}
+  %init = iree_tensor_ext.dispatch.tensor.load %0, offsets = [0, 0, 0, 0], sizes = [32, %n0, 7, %c8_vscale], strides = [1, 1, 1, 1] : !iree_tensor_ext.dispatch.tensor<readwrite:tensor<32x?x7x?xf32>>{%n0, %c8_vscale} -> tensor<32x?x7x?xf32>
+  %mmt4d = linalg.mmt4d ins(%arg0, %arg1 : tensor<32x128x7x1xf32>, tensor<?x128x?x1xf32>) outs(%init : tensor<32x?x7x?xf32>) -> tensor<32x?x7x?xf32>
+  return %mmt4d : tensor<32x?x7x?xf32>
+}
+//   CHECK-DAG: #[[CONFIG:.+]] = #iree_cpu.lowering_config<distribution = [32, 16, 0, 0, 0, 0], vector_common_parallel = [1, 1, 0, 7, [8], 0], vector_reduction = [0, 0, 1, 0, 0, 1]>
+//   CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<Mmt4dTilingExpert>>
+//       CHECK: func.func @mmt4d_tensors
+//  CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//       CHECK: linalg.mmt4d
+//  CHECK-SAME:     lowering_config = #[[CONFIG]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map = affine_map<()[s0] -> (256 ceildiv s0)>
+func.func @mmt4d_with_fill(%arg0 : tensor<32x128x7x1xf32>, %arg1 : tensor<?x128x?x1xf32>) -> tensor<32x?x7x?xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c8 = arith.constant 8 : index
+  %vscale = vector.vscale
+  %c8_vscale = arith.muli %vscale, %c8 : index
+  %0 = affine.apply #map()[%c8_vscale]
+  %init = tensor.empty(%0, %c8_vscale) : tensor<32x?x7x?xf32>
+  %fill = linalg.fill ins(%cst : f32) outs(%init : tensor<32x?x7x?xf32>) -> tensor<32x?x7x?xf32>
+  %mmt4d = linalg.mmt4d ins(%arg0, %arg1 : tensor<32x128x7x1xf32>, tensor<?x128x?x1xf32>) outs(%fill : tensor<32x?x7x?xf32>) -> tensor<32x?x7x?xf32>
+  return %mmt4d : tensor<32x?x7x?xf32>
+}
+//   CHECK-DAG: #[[CONFIG1:.+]] = #iree_cpu.lowering_config<vector_common_parallel = [1, 1, 7, [8]]>
+//   CHECK-DAG: #[[CONFIG2:.+]] = #iree_cpu.lowering_config<distribution = [32, 16, 0, 0, 0, 0], vector_common_parallel = [1, 1, 0, 7, [8], 0], vector_reduction = [0, 0, 1, 0, 0, 1]>
+//   CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<Mmt4dTilingExpert>>
+//       CHECK: func.func @mmt4d_with_fill
+//  CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//       CHECK: linalg.fill
+//  CHECK-SAME:     lowering_config = #[[CONFIG1]]
+//       CHECK: linalg.mmt4d
+//  CHECK-SAME:     lowering_config = #[[CONFIG2]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map = affine_map<()[s0] -> (320 ceildiv s0)>
+func.func @unpack(%arg0 : tensor<128x10x?x8x?xf32>) -> tensor<128x80x320xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %c8 = arith.constant 8 : index
+  %vscale = vector.vscale
+  %c8_vscale = arith.muli %vscale, %c8 : index
+  %init = tensor.empty() : tensor<128x80x320xf32>
+  %unpack = linalg.unpack %arg0 outer_dims_perm = [0, 1, 2] inner_dims_pos = [1, 2] inner_tiles = [8, %c8_vscale] into %init : tensor<128x10x?x8x?xf32> -> tensor<128x80x320xf32>
+  return %unpack : tensor<128x80x320xf32>
+}
+//   CHECK-DAG: #[[CONFIG:.+]] = #iree_cpu.lowering_config<distribution = [64, 40, 64], vector_common_parallel = [1, 8, [8]]>
+//   CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<DataTiling>>
+//
+// For RVV, we do not decompose unpacks.
+//   CHECK-NOT: enable_loop_peeling
+//       CHECK: func.func @unpack
+//  CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//       CHECK:   linalg.unpack
+//  CHECK-SAME:       lowering_config = #[[CONFIG]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+func.func @unpack_outer_dynamic(%arg0 : tensor<?x?x32x?xf32>, %dim0 : index, %dim1 : index) -> tensor<?x?xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %c8 = arith.constant 8 : index
+  %vscale = vector.vscale
+  %c8_vscale = arith.muli %vscale, %c8 : index
+  %init = tensor.empty(%dim0, %dim1) : tensor<?x?xf32>
+  %unpack = linalg.unpack %arg0 inner_dims_pos = [0, 1] inner_tiles = [32, %c8_vscale] into %init : tensor<?x?x32x?xf32> -> tensor<?x?xf32>
+  return %unpack : tensor<?x?xf32>
+}
+//   CHECK-DAG: #[[CONFIG:.+]] = #iree_cpu.lowering_config<distribution = [64, 64], vector_common_parallel = [32, [8]]>
+//   CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<DataTiling>>
+//
+// For RVV, we do not decompose unpacks.
+//   CHECK-NOT: enable_loop_peeling
+//       CHECK: func.func @unpack_outer_dynamic
+//  CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//       CHECK:   linalg.unpack
+//  CHECK-SAME:       lowering_config = #[[CONFIG]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map_pack = affine_map<()[s0] -> (48 ceildiv s0)>
+func.func @pack(%arg0: tensor<20x48xf32>) -> tensor<2x?x16x?xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %cst = arith.constant 0.000000e+00 : f32
+  %c16 = arith.constant 16 : index
+  %vscale = vector.vscale
+  %c16_vscale = arith.muli %vscale, %c16 : index
+  %outer1 = affine.apply #map_pack()[%c16_vscale]
+  %empty = tensor.empty(%outer1, %c16_vscale) : tensor<2x?x16x?xf32>
+  %pack = linalg.pack %arg0 padding_value(%cst : f32) inner_dims_pos = [0, 1] inner_tiles = [16, %c16_vscale] into %empty : tensor<20x48xf32> -> tensor<2x?x16x?xf32>
+  return %pack : tensor<2x?x16x?xf32>
+}
+//  CHECK-DAG: #[[CONFIG:.+]] = #iree_cpu.lowering_config<distribution = [1, 64], vector_common_parallel = [1, 1]>
+//  CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<DataTiling>>
+//      CHECK: func.func @pack(
+// CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//      CHECK:   linalg.pack
+// CHECK-SAME:       lowering_config = #[[CONFIG]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map_elem_pack = affine_map<()[s0] -> (384 ceildiv s0)>
+#map = affine_map<(d0, d1) -> (d0, d1)>
+func.func @elem_pack(%arg0: tensor<128x384xf32>) -> tensor<16x?x8x?xf32> attributes {hal.executable.target = #executable_target_riscv64} {
+  %empty = tensor.empty() : tensor<128x384xf32>
+  %c16 = arith.constant 16 : index
+  %vscale = vector.vscale
+  %c16_vscale = arith.muli %vscale, %c16 : index
+  %outer1 = affine.apply #map_elem_pack()[%c16_vscale]
+  %filled = linalg.generic {indexing_maps = [#map, #map], iterator_types = ["parallel", "parallel"]} ins(%arg0 : tensor<128x384xf32>) outs(%empty : tensor<128x384xf32>) {
+  ^bb0(%in: f32, %out: f32):
+    %sum = arith.addf %in, %in : f32
+    linalg.yield %sum : f32
+  } -> tensor<128x384xf32>
+  %dest = tensor.empty(%outer1, %c16_vscale) : tensor<16x?x8x?xf32>
+  %pack = linalg.pack %filled inner_dims_pos = [0, 1] inner_tiles = [8, %c16_vscale] into %dest : tensor<128x384xf32> -> tensor<16x?x8x?xf32>
+  return %pack : tensor<16x?x8x?xf32>
+}
+//  CHECK-DAG: #[[CONFIG1:.+]] = #iree_cpu.lowering_config<distribution = [64, 64], vector_common_parallel = [8, [16]]>
+//  CHECK-DAG: #[[CONFIG2:.+]] = #iree_cpu.lowering_config<vector_common_parallel = [1, 1]>
+//  CHECK-DAG: #[[TRANSLATION:.+]] = #iree_codegen.translation_info<pipeline = #iree_cpu.pipeline<DoubleTilingExpert>>
+//      CHECK: func.func @elem_pack(
+// CHECK-SAME:     translation_info = #[[TRANSLATION]]
+//      CHECK:   linalg.generic
+// CHECK-SAME:       lowering_config = #[[CONFIG1]]
+//      CHECK:   linalg.pack
+// CHECK-SAME:       lowering_config = #[[CONFIG2]]
+
+// -----
+
+#executable_target_riscv64 = #hal.executable.target<"llvm-cpu", "embedded-elf-riscv_64", {cpu_features = "+zvfh,+v", data_layout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", native_vector_size = 16 : index, target_triple = "riscv64-unknown-unknown-eabi-elf"}>
+#map = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>
+#map2 = affine_map<()[s0] -> (10240 ceildiv s0)>
+func.func @mmt4d_generic_unpack_pack(%arg0: tensor<5x4096x7x1xf16>, %arg1: tensor<?x4096x?x1xf16>) -> tensor<5x10240x7x1xf16> attributes {hal.executable.target = #executable_target_riscv64} {
+  %cst = arith.constant 0.000000e+00 : f16
+  %cst_0 = arith.constant 0.000000e+00 : f32
+
+  %c8 = arith.constant 8 : index
+  %vscale = vector.vscale
+  %c8_vscale = arith.muli %vscale, %c8 : index
+  %n0 = affine.apply #map2()[%c8_vscale]
+
+  %0 = tensor.empty(%n0, %c8_vscale) : tensor<5x?x7x?xf16>
+  %1 = tensor.empty(%n0, %c8_vscale) : tensor<5x?x7x?xf32>
+  %2 = linalg.fill ins(%cst_0 : f32) outs(%1 : tensor<5x?x7x?xf32>) -> tensor<5x?x7x?xf32>
+  %3 = linalg.mmt4d ins(%arg0, %arg1 : tensor<5x4096x7x1xf16>, tensor<?x4096x?x1xf16>) outs(%2 : tensor<5x?x7x?xf32>) -> tensor<5x?x7x?xf32>
+  %4 = linalg.generic {indexing_maps = [#map, #map], iterator_types = ["parallel", "parallel", "parallel", "parallel"]} ins(%3 : tensor<5x?x7x?xf32>) outs(%0 : tensor<5x?x7x?xf16>) {
+  ^bb0(%in: f32, %out: f16):
+    %7 = arith.truncf %in : f32 to f16
+    linalg.yield %7 : f16
+  } -> tensor<5x?x7x?xf16>
+  %5 = tensor.empty() : tensor<33x10240xf16>
+  %unpack = linalg.unpack %4 outer_dims_perm = [0, 1] inner_dims_pos = [0, 1] inner_tiles = [7, %c8_vscale] into %5 : tensor<5x?x7x?xf16> -> tensor<33x10240xf16>
+  %6 = tensor.empty() : tensor<5x10240x7x1xf16>
+  %pack = linalg.pack %unpack padding_value(%cst : f16) outer_dims_perm = [0, 1] inner_dims_pos = [0, 1] inner_tiles = [7, 1] into %6 : tensor<33x10240xf16> -> tensor<5x10240x7x1xf16>
+  return %pack : tensor<5x10240x7x1xf16>
+}
+// CHECK-DAG:   #[[$CONFIG0:.+]] = #iree_cpu.lowering_config<vector_common_parallel = [1, 1, 7, [8]]>
+// CHECK-DAG:   #[[$CONFIG1:.+]] = #iree_cpu.lowering_config<distribution = [5, 1, 0, 0, 0, 0], vector_common_parallel = [1, 1, 0, 7, [8], 0], vector_reduction = [0, 0, 1, 0, 0, 1]>
+// CHECK-DAG:   #[[$CONFIG2:.+]] = #iree_cpu.lowering_config<vector_common_parallel = [1, 1]>
+// CHECK-LABEL: func.func @mmt4d_generic_unpack_pack(
+// CHECK:         linalg.fill
+// CHECK-SAME:      {lowering_config = #[[$CONFIG0]]}
+// CHECK:         linalg.mmt4d
+// CHECK-SAME:      {lowering_config = #[[$CONFIG1]]}
+// CHECK:         linalg.generic
+// CHECK-SAME:      {lowering_config = #[[$CONFIG0]]}
+// CHECK:         linalg.unpack
+// CHECK-SAME:      {lowering_config = #[[$CONFIG2]]}
+// CHECK:         linalg.pack
+// CHECK-NOT:      lowering_config