[Mlir-commits] [mlir] [mlir][vector] Add an eliminate-vector-masks pass (PR #226517)
Dhairyashil R G
llvmlistbot at llvm.org
Fri Sep 25 08:06:10 PDT 2026
https://github.com/dhairyashilRG created https://github.com/llvm/llvm-project/pull/226517
`eliminateVectorMasks` had no in-tree caller other than a test pass, so no pipeline could use it. This exposes it as an opt-in `-eliminate-vector-masks` pass and drops the test pass.
The pass rewrites `vector.create_mask` ops that can be proven all-true into `vector.constant_mask`; canonicalization then folds those away, so a masked transfer becomes an unmasked one. Mask sizes are bounded with `ValueBoundsOpInterface`, so a loop-derived size like `%dim - %iv` can be proven.
Scalable dimensions need a `vscale` range, given by `vscale-min`/`vscale-max`. Leaving them unset means fixed-size reasoning only; 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, which shows the pass behaves as the test pass did.
Follow-up to #221595, and the pass promised when #215340 was closed.
Context: https://discourse.llvm.org/t/rfc-should-vector-transfer-read-write-keep-in-bounds-measurements-on-the-masking-alternative/91649
Assisted-by: Claude
>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] [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 9431a4d8e240f..d0abee7f87926 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 e6ee2b047de07..94111332ff5d3 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 0000000000000..a373cb6103db1
--- /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 002c502dc5fdb..22fe1aa8c83b1 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 f26195793e0a2..ab4386167f7ab 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
More information about the Mlir-commits
mailing list