[LLVMCPU] add inner-tile-alignment hint attribute (#24806)

Introduce the representation for per-tiling-level inner-tile alignment
hints
as introduced in llvm/llvm-project#204007:

	#iree_cpu.inner_tile_alignments<level = [Unknown|Multiple|Equal, ...]>

These will only be set on pack and unpack operations with scalable inner
tile sizes, and will not be for other operations that do not need them.

Assisted-by: Claude Code

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

Signed-off-by: Ege Beysel <beyselege@gmail.com>
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/BUILD.bazel b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/BUILD.bazel
index 8ef2115..ac9b672 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/BUILD.bazel
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/BUILD.bazel
@@ -77,6 +77,7 @@
         "@llvm-project//mlir:Parser",
         "@llvm-project//mlir:Support",
         "@llvm-project//mlir:TensorDialect",
+        "@llvm-project//mlir:TilingInterface",
         "@llvm-project//mlir:VectorDialect",
     ],
 )
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/CMakeLists.txt
index 6ff9835..ea6059e 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/CMakeLists.txt
@@ -40,6 +40,7 @@
     MLIRParser
     MLIRSupport
     MLIRTensorDialect
+    MLIRTilingInterface
     MLIRVectorDialect
     iree::compiler::Codegen::Dialect::Codegen::IR::IREECodegenDialect
     iree::compiler::Codegen::Dialect::Codegen::Utils
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
index 8997f88..ed1d263 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.cpp
@@ -28,6 +28,7 @@
 #include "mlir/IR/BuiltinTypes.h"
 #include "mlir/IR/Operation.h"
 #include "mlir/IR/ValueRange.h"
+#include "mlir/Interfaces/TilingInterface.h"
 
 #include "iree/compiler/Codegen/Dialect/CPU/IR/IREECPUEnums.cpp.inc"
 #define GET_ATTRDEF_CLASSES
@@ -323,6 +324,110 @@
 }
 
 //===----------------------------------------------------------------------===//
+// InnerTileAlignmentsAttr
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+InnerTileAlignmentsAttr::verify(function_ref<InFlightDiagnostic()> emitError,
+                                DictionaryAttr alignments) {
+  if (!alignments || alignments.empty()) {
+    return emitError() << "expected at least one tiling level";
+  }
+  for (NamedAttribute entry : alignments) {
+    auto arr = dyn_cast<DenseI64ArrayAttr>(entry.getValue());
+    if (!arr) {
+      return emitError()
+             << "expected a per-dimension InnerTileAlignment array for '"
+             << entry.getName().getValue() << "'";
+    }
+    for (int64_t value : arr.asArrayRef()) {
+      if (!mlir::isValidInnerTileAlignment(value)) {
+        return emitError() << "invalid InnerTileAlignment value: " << value;
+      }
+    }
+  }
+  return success();
+}
+
+Attribute InnerTileAlignmentsAttr::parse(AsmParser &parser, Type) {
+  MLIRContext *ctx = parser.getContext();
+  if (parser.parseLess()) {
+    return {};
+  }
+  SmallVector<NamedAttribute> items;
+  bool first = true;
+  while (failed(parser.parseOptionalGreater())) {
+    if (!first && parser.parseComma()) {
+      return {};
+    }
+    first = false;
+    // <level_name> = [Unknown, Equal, ...]
+    std::string keyStr;
+    if (parser.parseKeywordOrString(&keyStr) || parser.parseEqual()) {
+      return {};
+    }
+    SmallVector<int64_t> alignments;
+    if (parser.parseCommaSeparatedList(
+            AsmParser::Delimiter::Square, [&]() -> ParseResult {
+              StringRef keyword;
+              if (parser.parseKeyword(&keyword)) {
+                return failure();
+              }
+              std::optional<mlir::InnerTileAlignment> kind =
+                  mlir::symbolizeInnerTileAlignment(keyword);
+              if (!kind) {
+                return parser.emitError(parser.getCurrentLocation(),
+                                        "expected an InnerTileAlignment "
+                                        "(Unknown|Multiple|Equal), got: ")
+                       << keyword;
+              }
+              alignments.push_back(static_cast<int64_t>(*kind));
+              return success();
+            })) {
+      return {};
+    }
+    items.emplace_back(StringAttr::get(ctx, keyStr),
+                       DenseI64ArrayAttr::get(ctx, alignments));
+  }
+  return parser.getChecked<InnerTileAlignmentsAttr>(
+      ctx, DictionaryAttr::get(ctx, items));
+}
+
+void InnerTileAlignmentsAttr::print(AsmPrinter &printer) const {
+  printer << "<";
+  llvm::interleaveComma(getAlignments(), printer, [&](NamedAttribute entry) {
+    // `.str()` avoids wrapping the tiling-level key with `"`.
+    printer << entry.getName().str() << " = [";
+    ArrayRef<int64_t> alignments =
+        cast<DenseI64ArrayAttr>(entry.getValue()).asArrayRef();
+    llvm::interleaveComma(alignments, printer, [&](int64_t value) {
+      printer << mlir::stringifyInnerTileAlignment(
+          static_cast<mlir::InnerTileAlignment>(value));
+    });
+    printer << "]";
+  });
+  printer << ">";
+}
+
+void InnerTileAlignmentsAttr::setOnOp(
+    Operation *op,
+    ArrayRef<std::pair<TilingLevel, SmallVector<int64_t>>> perLevel) {
+  MLIRContext *ctx = op->getContext();
+  SmallVector<NamedAttribute> entries;
+  entries.reserve(perLevel.size());
+  for (auto &[level, alignments] : perLevel) {
+    entries.emplace_back(StringAttr::get(ctx, getTilingLevelName(level)),
+                         DenseI64ArrayAttr::get(ctx, alignments));
+  }
+  op->setAttr(getMnemonic(), InnerTileAlignmentsAttr::get(
+                                 ctx, DictionaryAttr::get(ctx, entries)));
+}
+
+InnerTileAlignmentsAttr InnerTileAlignmentsAttr::getFromOp(Operation *op) {
+  return op->getAttrOfType<InnerTileAlignmentsAttr>(getMnemonic());
+}
+
+//===----------------------------------------------------------------------===//
 // CPU MMA intrinsic layout (MxNxK shape and element types)
 //===----------------------------------------------------------------------===//
 
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.td b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.td
index 9629fd8..5fcfe91 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.td
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/IREECPUAttrs.td
@@ -249,6 +249,35 @@
   let genVerifyDecl = 1;
 }
 
+def IREECPU_InnerTileAlignmentsAttr :
+    AttrDef<IREECPU_Dialect, "InnerTileAlignments"> {
+  let mnemonic = "inner_tile_alignments";
+  let summary = [{Per-tiling-level inner-tile alignment hints for pack/unpack ops.}];
+  let description = [{
+    A discardable hint attached to a scalable `linalg.pack`/`linalg.unpack`
+    during tile-size selection, recording, per tiling level, the
+    `mlir::InnerTileAlignment` (`Unknown`, `Multiple` or `Equal`) of each
+    iteration-domain dimension.
+  }];
+  let parameters = (ins
+    AttrParameter<"DictionaryAttr",
+        "Maps a tiling-level name to its per-dim InnerTileAlignment array.">:$alignments
+  );
+  let extraClassDeclaration = [{
+    /// Builds a hint from per-tiling-level `InnerTileAlignment` values and attaches
+    ///  it to `op` under the attribute's mnemonic, overwriting any existing one.
+    static void setOnOp(
+        ::mlir::Operation *op,
+        ::llvm::ArrayRef<
+            ::std::pair<TilingLevel, ::llvm::SmallVector<int64_t>>> perLevel);
+
+    /// Returns the hint attached to `op`, or a null attribute if it has none.
+    static InnerTileAlignmentsAttr getFromOp(::mlir::Operation *op);
+  }];
+  let hasCustomAssemblyFormat = 1;
+  let genVerifyDecl = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // Encoding Resolvers.
 //===----------------------------------------------------------------------===//
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/invalid.mlir b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/invalid.mlir
index 05e1980..0a6b31f 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/invalid.mlir
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/invalid.mlir
@@ -122,3 +122,23 @@
   } : tensor<1x1x1x1xf32>, tensor<1x1x4x1xf32> into tensor<1x1x1x?xf32>
   return %0 : tensor<1x1x1x?xf32>
 }
+
+// -----
+
+// The inner-tile alignment array only accepts InnerTileAlignment keywords
+// (Unknown, Multiple, Equal).
+// expected-error@+1 {{expected an InnerTileAlignment}}
+#invalid_inner_tile_alignment_name = #iree_cpu.inner_tile_alignments<vector_common_parallel = [Bogus]>
+
+// -----
+
+// Each alignment must be encapsulated in an array, e.g. `[Multiple]`, not
+// given as a bare keyword.
+// expected-error@+1 {{}}
+#invalid_inner_tile_alignment_value = #iree_cpu.inner_tile_alignments<vector_common_parallel = Multiple>
+
+// -----
+
+// An empty hint (no tiling levels) is meaningless and is rejected.
+// expected-error@+1 {{expected at least one tiling level}}
+#invalid_inner_tile_alignment_empty = #iree_cpu.inner_tile_alignments<>
diff --git a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/roundtrip.mlir b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/roundtrip.mlir
index 29d2d44..5a2845a 100644
--- a/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/roundtrip.mlir
+++ b/compiler/src/iree/compiler/Codegen/Dialect/CPU/IR/test/roundtrip.mlir
@@ -82,3 +82,35 @@
 }
 // CHECK-LABEL: @test_ukernel_provider()
 // CHECK-SAME:    iree_codegen.ukernel_provider = #iree_cpu.ukernel_provider
+
+// -----
+
+// Round-trip the inner-tile alignment hint attribute. It records, per tiling
+// level, the InnerTileAlignment (Unknown|Multiple|Equal) of each
+// iteration-domain dimension.
+func.func @test_inner_tile_alignments() attributes {
+    inner_tile_alignments =
+        #iree_cpu.inner_tile_alignments<vector_common_parallel = [Unknown, Equal]>} {
+  return
+}
+// CHECK-LABEL: @test_inner_tile_alignments()
+// CHECK-SAME:    inner_tile_alignments = #iree_cpu.inner_tile_alignments<
+// CHECK-SAME:      vector_common_parallel = [Unknown, Equal]>
+
+// -----
+
+// Multiple levels and all three alignment kinds, each level carrying one entry
+// per iteration-domain dimension (here 2). Levels are printed sorted by name,
+// regardless of input order.
+func.func @test_inner_tile_alignments_multi_level() attributes {
+    inner_tile_alignments = #iree_cpu.inner_tile_alignments<
+      vector_inner_parallel = [Equal, Unknown],
+      vector_common_parallel = [Multiple, Unknown],
+      distribution = [Unknown, Equal]>} {
+  return
+}
+// CHECK-LABEL: @test_inner_tile_alignments_multi_level()
+// CHECK-SAME:    inner_tile_alignments = #iree_cpu.inner_tile_alignments<
+// CHECK-SAME:      distribution = [Unknown, Equal],
+// CHECK-SAME:      vector_common_parallel = [Multiple, Unknown],
+// CHECK-SAME:      vector_inner_parallel = [Equal, Unknown]>