[Mlir-commits] [mlir] [mlir][vector] Add an eliminate-vector-masks pass (PR #226517)

Dhairyashil R G llvmlistbot at llvm.org
Sat Sep 26 12:36:40 PDT 2026


https://github.com/dhairyashilRG updated https://github.com/llvm/llvm-project/pull/226517

>From 0721679dfd59de8102423d7e19c331f6af489ea8 Mon Sep 17 00:00:00 2001
From: Dhairyashil R G <dhairyashil25 at gmail.com>
Date: Fri, 25 Sep 2026 19:29:29 +0530
Subject: [PATCH 1/2] [mlir][vector] Add an eliminate-vector-masks pass

`eliminateVectorMasks` had no in-tree caller other than a test pass, so no
pipeline could use it. Expose it as an opt-in `-eliminate-vector-masks` pass and
drop the test pass.

The pass rewrites `vector.create_mask` operations that can be proven all-true
into `vector.constant_mask`, which canonicalization then folds away, turning a
masked transfer into an unmasked one. Mask sizes are bounded with
`ValueBoundsOpInterface`, so a size derived from a loop, such as `%dim - %iv`,
can be proven.

Proving a scalable dimension needs the range of `vscale`, given by the
`vscale-min` and `vscale-max` options. Leaving them unset means fixed-size
reasoning only, which suits a target without scalable vectors; a half-specified
or inverted range is rejected rather than silently ignored.

`eliminate-masks.mlir` keeps its checks and only switches its two RUN lines, so
the pass is shown to behave as the test pass did.

Context:
https://discourse.llvm.org/t/rfc-should-vector-transfer-read-write-keep-in-bounds-measurements-on-the-masking-alternative/91649

Assisted-by: Claude
---
 .../mlir/Dialect/Vector/Transforms/Passes.td  | 24 +++++++++++++
 .../Transforms/VectorMaskElimination.cpp      | 33 +++++++++++++++++
 .../eliminate-masks-invalid-options.mlir      | 11 ++++++
 mlir/test/Dialect/Vector/eliminate-masks.mlir |  4 +--
 .../Dialect/Vector/TestVectorTransforms.cpp   | 35 -------------------
 5 files changed, 70 insertions(+), 37 deletions(-)
 create mode 100644 mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir

diff --git a/mlir/include/mlir/Dialect/Vector/Transforms/Passes.td b/mlir/include/mlir/Dialect/Vector/Transforms/Passes.td
index 9431a4d8e240f7..d0abee7f87926d 100644
--- a/mlir/include/mlir/Dialect/Vector/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/Vector/Transforms/Passes.td
@@ -11,6 +11,30 @@
 
 include "mlir/Pass/PassBase.td"
 
+def EliminateVectorMasks : Pass<"eliminate-vector-masks", "func::FuncOp"> {
+  let summary = "Replace masks that are provably all-true with constant masks";
+  let description = [{
+    Rewrites `vector.create_mask` operations that can be proven all-true into
+    `vector.constant_mask`. Canonicalization then folds those away, turning a
+    masked transfer into an unmasked one.
+
+    A mask dimension is all-true when its size is at least the size of the
+    corresponding vector dimension. Sizes are bounded with
+    `ValueBoundsOpInterface`, so sizes derived from a loop, such as
+    `%dim - %iv`, can be proven too.
+
+    Proving a scalable dimension needs the range of `vscale`: pass `vscale-min`
+    and `vscale-max` when it is known for the target (1 and 16 for SVE, say).
+    Without a range, only fixed-size dimensions can be proven.
+  }];
+  let options = [
+    Option<"vscaleMin", "vscale-min", "unsigned", /*default=*/"0",
+           "Minimum possible value of vscale, or 0 when unknown.">,
+    Option<"vscaleMax", "vscale-max", "unsigned", /*default=*/"0",
+           "Maximum possible value of vscale, or 0 when unknown.">,
+  ];
+}
+
 def LowerVectorMaskPass : Pass<"lower-vector-mask", "func::FuncOp"> {
   let summary = "Lower 'vector.mask' operations";
   let constructor = "mlir::vector::createLowerVectorMaskPass()";
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
index e6ee2b047de07d..94111332ff5d30 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
@@ -8,9 +8,19 @@
 
 #include "mlir/Dialect/Utils/StaticValueUtils.h"
 #include "mlir/Dialect/Vector/IR/ScalableValueBoundsConstraintSet.h"
+#include "mlir/Dialect/Vector/Transforms/Passes.h"
 #include "mlir/Dialect/Vector/Transforms/VectorTransforms.h"
 #include "mlir/Interfaces/FunctionInterfaces.h"
 
+namespace mlir {
+namespace vector {
+
+#define GEN_PASS_DEF_ELIMINATEVECTORMASKS
+#include "mlir/Dialect/Vector/Transforms/Passes.h.inc"
+
+} // namespace vector
+} // namespace mlir
+
 using namespace mlir;
 using namespace mlir::vector;
 namespace {
@@ -136,4 +146,27 @@ void eliminateVectorMasks(IRRewriter &rewriter, FunctionOpInterface function,
     (void)resolveAllTrueCreateMaskOp(rewriter, mask, vscaleRange);
 }
 
+namespace {
+struct EliminateVectorMasksPass
+    : public impl::EliminateVectorMasksBase<EliminateVectorMasksPass> {
+  using Base::Base;
+
+  void runOnOperation() override {
+    std::optional<VscaleRange> vscaleRange;
+    if (vscaleMin || vscaleMax) {
+      if (!vscaleMin || !vscaleMax || vscaleMin > vscaleMax) {
+        getOperation()->emitError()
+            << "expected 'vscale-min' and 'vscale-max' to both be set, with "
+               "'vscale-min' <= 'vscale-max'";
+        return signalPassFailure();
+      }
+      vscaleRange = VscaleRange{vscaleMin, vscaleMax};
+    }
+
+    IRRewriter rewriter(&getContext());
+    eliminateVectorMasks(rewriter, getOperation(), vscaleRange);
+  }
+};
+} // namespace
+
 } // namespace mlir::vector
diff --git a/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir b/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir
new file mode 100644
index 00000000000000..a373cb6103db1c
--- /dev/null
+++ b/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir
@@ -0,0 +1,11 @@
+// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=2" 2>&1 | FileCheck %s
+// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=8 vscale-max=4" 2>&1 | FileCheck %s
+
+/// A half-specified or inverted vscale range is rejected rather than silently
+/// ignored, which would leave scalable masks unproven for no visible reason.
+
+// CHECK: error: expected 'vscale-min' and 'vscale-max' to both be set, with 'vscale-min' <= 'vscale-max'
+func.func @vscale_range(%n: index) -> vector<[4]xi1> {
+  %mask = vector.create_mask %n : vector<[4]xi1>
+  return %mask : vector<[4]xi1>
+}
diff --git a/mlir/test/Dialect/Vector/eliminate-masks.mlir b/mlir/test/Dialect/Vector/eliminate-masks.mlir
index 002c502dc5fdb9..22fe1aa8c83b13 100644
--- a/mlir/test/Dialect/Vector/eliminate-masks.mlir
+++ b/mlir/test/Dialect/Vector/eliminate-masks.mlir
@@ -1,5 +1,5 @@
-// RUN: mlir-opt %s -split-input-file -test-eliminate-vector-masks | FileCheck %s --check-prefixes=ALL,WITH-RANGE
-// RUN: mlir-opt %s -split-input-file -test-eliminate-vector-masks=fixed-size | FileCheck %s --check-prefixes=ALL,NO-RANGE
+// RUN: mlir-opt %s -split-input-file -eliminate-vector-masks="vscale-min=1 vscale-max=16" | FileCheck %s --check-prefixes=ALL,WITH-RANGE
+// RUN: mlir-opt %s -split-input-file -eliminate-vector-masks | FileCheck %s --check-prefixes=ALL,NO-RANGE
 
 // Each scalable test below is paired with a fixed-size equivalent, so that both
 // widths get the same coverage. Only the one case that needs a vscale range to
diff --git a/mlir/test/lib/Dialect/Vector/TestVectorTransforms.cpp b/mlir/test/lib/Dialect/Vector/TestVectorTransforms.cpp
index f26195793e0a21..ab4386167f7abe 100644
--- a/mlir/test/lib/Dialect/Vector/TestVectorTransforms.cpp
+++ b/mlir/test/lib/Dialect/Vector/TestVectorTransforms.cpp
@@ -1040,39 +1040,6 @@ struct TestVectorLinearize final
   }
 };
 
-struct TestEliminateVectorMasks
-    : public PassWrapper<TestEliminateVectorMasks,
-                         OperationPass<func::FuncOp>> {
-  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestEliminateVectorMasks)
-
-  TestEliminateVectorMasks() = default;
-  TestEliminateVectorMasks(const TestEliminateVectorMasks &pass)
-      : PassWrapper(pass) {}
-
-  Option<unsigned> vscaleMin{
-      *this, "vscale-min", llvm::cl::desc("Minimum possible value of vscale."),
-      llvm::cl::init(1)};
-  Option<unsigned> vscaleMax{
-      *this, "vscale-max", llvm::cl::desc("Maximum possible value of vscale."),
-      llvm::cl::init(16)};
-  Option<bool> fixedSize{
-      *this, "fixed-size",
-      llvm::cl::desc("Run without a vscale range, as fixed-size code would."),
-      llvm::cl::init(false)};
-
-  StringRef getArgument() const final { return "test-eliminate-vector-masks"; }
-  StringRef getDescription() const final {
-    return "Test eliminating vector masks";
-  }
-  void runOnOperation() override {
-    IRRewriter rewriter(&getContext());
-    std::optional<VscaleRange> vscaleRange;
-    if (!fixedSize)
-      vscaleRange = VscaleRange{vscaleMin, vscaleMax};
-    eliminateVectorMasks(rewriter, getOperation(), vscaleRange);
-  }
-};
-
 struct TestVectorShuffleLowering
     : public PassWrapper<TestVectorShuffleLowering,
                          OperationPass<func::FuncOp>> {
@@ -1136,8 +1103,6 @@ void registerTestVectorLowerings() {
   PassRegistration<TestVectorLinearize>();
 
   PassRegistration<TestVectorBitWidthLinearize>();
-
-  PassRegistration<TestEliminateVectorMasks>();
 }
 } // namespace test
 } // namespace mlir

>From a926efee3bae4041cad9d77b38260382754e1624 Mon Sep 17 00:00:00 2001
From: Dhairyashil R G <dhairyashil25 at gmail.com>
Date: Sat, 26 Sep 2026 23:26:32 +0530
Subject: [PATCH 2/2] [mlir][vector] Address review: report a bad vscale range
 once, and say why

Check `vscale-min`/`vscale-max` in `initialize()` instead of
`runOnOperation()`, so a bad range is reported once rather than once per
function, and name the given values in the error, so an explicit
`vscale-min=0` is not told to set both options.

Assisted-by: Claude
---
 .../Transforms/VectorMaskElimination.cpp      | 24 ++++++++++++-------
 .../eliminate-masks-invalid-options.mlir      | 16 ++++++++++---
 2 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
index 94111332ff5d30..466b04c4c51a08 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorMaskElimination.cpp
@@ -151,17 +151,25 @@ struct EliminateVectorMasksPass
     : public impl::EliminateVectorMasksBase<EliminateVectorMasksPass> {
   using Base::Base;
 
+  // Checked here rather than in runOnOperation so that a bad range is reported
+  // once, not once per function.
+  LogicalResult initialize(MLIRContext *context) override {
+    bool unset = !vscaleMin && !vscaleMax;
+    bool valid = vscaleMin && vscaleMax && vscaleMin <= vscaleMax;
+    if (unset || valid)
+      return success();
+    return emitError(UnknownLoc::get(context))
+           << "invalid vscale range 'vscale-min="
+           << static_cast<unsigned>(vscaleMin)
+           << " vscale-max=" << static_cast<unsigned>(vscaleMax)
+           << "': expected both to be 0 (unknown), or both non-zero with "
+              "'vscale-min' <= 'vscale-max'";
+  }
+
   void runOnOperation() override {
     std::optional<VscaleRange> vscaleRange;
-    if (vscaleMin || vscaleMax) {
-      if (!vscaleMin || !vscaleMax || vscaleMin > vscaleMax) {
-        getOperation()->emitError()
-            << "expected 'vscale-min' and 'vscale-max' to both be set, with "
-               "'vscale-min' <= 'vscale-max'";
-        return signalPassFailure();
-      }
+    if (vscaleMin && vscaleMax)
       vscaleRange = VscaleRange{vscaleMin, vscaleMax};
-    }
 
     IRRewriter rewriter(&getContext());
     eliminateVectorMasks(rewriter, getOperation(), vscaleRange);
diff --git a/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir b/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir
index a373cb6103db1c..a25830e44826f8 100644
--- a/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir
+++ b/mlir/test/Dialect/Vector/eliminate-masks-invalid-options.mlir
@@ -1,11 +1,21 @@
-// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=2" 2>&1 | FileCheck %s
-// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=8 vscale-max=4" 2>&1 | FileCheck %s
+// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=2" 2>&1 | FileCheck %s --check-prefixes=CHECK,HALF
+// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=0 vscale-max=4" 2>&1 | FileCheck %s --check-prefixes=CHECK,ZERO
+// RUN: not mlir-opt %s -eliminate-vector-masks="vscale-min=8 vscale-max=4" 2>&1 | FileCheck %s --check-prefixes=CHECK,INVERTED
 
 /// A half-specified or inverted vscale range is rejected rather than silently
 /// ignored, which would leave scalable masks unproven for no visible reason.
+/// The error is reported once, not once per function.
 
-// CHECK: error: expected 'vscale-min' and 'vscale-max' to both be set, with 'vscale-min' <= 'vscale-max'
+// HALF:       error: invalid vscale range 'vscale-min=2 vscale-max=0':
+// ZERO:       error: invalid vscale range 'vscale-min=0 vscale-max=4':
+// INVERTED:   error: invalid vscale range 'vscale-min=8 vscale-max=4':
+// CHECK-SAME: expected both to be 0 (unknown), or both non-zero with 'vscale-min' <= 'vscale-max'
+// CHECK-NOT:  error:
 func.func @vscale_range(%n: index) -> vector<[4]xi1> {
   %mask = vector.create_mask %n : vector<[4]xi1>
   return %mask : vector<[4]xi1>
 }
+
+func.func @other() {
+  return
+}



More information about the Mlir-commits mailing list