[Mlir-commits] [mlir] [mlir][sparse] Fix mixed sparse-dense iterator lowering (PR #219753)
Kohei Yamaguchi
llvmlistbot at llvm.org
Mon Nov 2 22:13:41 PST 2026
https://github.com/sott0n updated https://github.com/llvm/llvm-project/pull/219753
>From bf7793f02179204f7de51b44a2172a8b0b704197 Mon Sep 17 00:00:00 2001
From: Kohei Yamaguchi <fix7211 at gmail.com>
Date: Sun, 30 Aug 2026 08:09:34 +0900
Subject: [PATCH 1/4] [mlir][sparse] Fix mixed sparse-dense iterator lowering
---
.../Transforms/SparseIterationToScf.cpp | 16 +++--
.../Transforms/Sparsification.cpp | 67 ++++++++++++-------
.../sparse_kernels_to_iterator.mlir | 55 +++++++++++++++
3 files changed, 107 insertions(+), 31 deletions(-)
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseIterationToScf.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseIterationToScf.cpp
index 09ca7474ccbbe8..6e793d96c33e51 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseIterationToScf.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseIterationToScf.cpp
@@ -228,9 +228,11 @@ class SparseIterateOpConverter : public OpConversionPattern<IterateOp> {
LogicalResult
matchAndRewrite(IterateOp op, OneToNOpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
- if (!op.getCrdUsedLvls().empty())
+ I64BitSet crdUsedLvls = op.getCrdUsedLvls();
+ if (crdUsedLvls.count() > 1 ||
+ (!crdUsedLvls.empty() && !crdUsedLvls[op.getSpaceDim() - 1]))
return rewriter.notifyMatchFailure(
- op, "non-empty coordinates list not implemented.");
+ op, "coordinates other than the final level are not implemented");
Location loc = op.getLoc();
@@ -254,13 +256,15 @@ class SparseIterateOpConverter : public OpConversionPattern<IterateOp> {
Block *block = rewriter.applySignatureConversion(
op.getBody(), signatureConversion, getTypeConverter());
+ bool usesCrd = !crdUsedLvls.empty();
ValueRange ret = genLoopWithIterator(
rewriter, loc, it.get(), ivs,
- [block](PatternRewriter &rewriter, Location loc, Region &loopBody,
- SparseIterator *it, ValueRange reduc) -> SmallVector<Value> {
+ [block, usesCrd](PatternRewriter &rewriter, Location loc,
+ Region &loopBody, SparseIterator *it,
+ ValueRange reduc) -> SmallVector<Value> {
SmallVector<Value> blockArgs(reduc);
- // TODO: Also appends coordinates if used.
- // blockArgs.push_back(it->deref(rewriter, loc));
+ if (usesCrd)
+ blockArgs.push_back(it->deref(rewriter, loc));
llvm::append_range(blockArgs, it->getCursor());
Block *dstBlock = &loopBody.getBlocks().front();
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
index 860ea68029c3a4..9b307b31de8214 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
@@ -1008,7 +1008,7 @@ static void endIf(CodegenEnv &env, OpBuilder &builder, scf::IfOp ifOp,
static bool getAllTidLvlsInLatPoints(
CodegenEnv &env, LatPointId li, LoopId curr,
- llvm::function_ref<void(TensorLevel, AffineExpr)> callback) {
+ llvm::function_ref<void(TensorLevel, AffineExpr, bool)> callback) {
const BitVector &simple = env.lat(li).simple;
const TensorId outTid = env.merger().getOutTensorID();
const std::optional<Level> outLvl = env.merger().getLvl(outTid, curr);
@@ -1020,7 +1020,8 @@ static bool getAllTidLvlsInLatPoints(
LevelType lt, bool isIdxReduc) {
if (simple[b]) {
if (isIdxReduc) {
- callback(env.makeTensorLevel(tid, *lvl), nullptr);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr,
+ /*isLoopCond=*/true);
numloopCond++;
return;
}
@@ -1044,10 +1045,12 @@ static bool getAllTidLvlsInLatPoints(
}
}
hasNonUnique = !isUniqueLT(lt) || hasNonUnique;
- callback(env.makeTensorLevel(tid, *lvl), nullptr);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr,
+ /*isLoopCond=*/true);
numloopCond++;
} else if (lt.hasDenseSemantic() || isIdxReduc) {
- callback(env.makeTensorLevel(tid, *lvl), nullptr);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr,
+ /*isLoopCond=*/false);
} else {
assert(isUndefLT(lt));
linalg::GenericOp op = env.op();
@@ -1082,7 +1085,8 @@ static bool getAllTidLvlsInLatPoints(
// level. We need to generate the address according to the
// affine expression. This is also the best place we can do it
// to avoid putting it inside inner loops.
- callback(env.makeTensorLevel(tid, l), exp);
+ callback(env.makeTensorLevel(tid, l), exp,
+ /*isLoopCond=*/false);
}
}
}
@@ -1097,14 +1101,16 @@ static bool getAllTidLvlsInLatPoints(
// TODO: we should avoid introducing corner cases for all-dense sparse
// tensors.
if (stt.hasEncoding() && stt.isAllDense())
- callback(env.makeTensorLevel(outTid, *outLvl), nullptr);
+ callback(env.makeTensorLevel(outTid, *outLvl), nullptr,
+ /*isLoopCond=*/false);
}
if (numloopCond == 0) {
// Corner cases where the loop bound is defined by a *unused* operand, in
// this case, we just generate a dense "fake" loop by iterating over the
// synthetic tensor.
- callback(env.makeTensorLevel(env.merger().getSynTensorID(), curr), nullptr);
+ callback(env.makeTensorLevel(env.merger().getSynTensorID(), curr), nullptr,
+ /*isLoopCond=*/true);
numloopCond++;
}
// If we just need to one loop conditions and the conditions is not imposed on
@@ -1129,14 +1135,16 @@ static bool startLoopSeq(CodegenEnv &env, OpBuilder &builder, ExprId exp,
const LatPointId l0 = env.set(lts)[0];
SmallVector<TensorLevel> tidLvls;
- getAllTidLvlsInLatPoints(env, l0, curr, [&](TensorLevel tl, AffineExpr) {
- // TODO: remove this! The same tensor level might be added for multiple
- // times due to the special handling for all-dense "sparse" output tensor
- // (see L1038).
- if (llvm::is_contained(tidLvls, tl))
- return;
- tidLvls.emplace_back(tl);
- });
+ getAllTidLvlsInLatPoints(env, l0, curr,
+ [&](TensorLevel tl, AffineExpr, bool) {
+ // TODO: remove this! The same tensor level might
+ // be added for multiple times due to the special
+ // handling for all-dense "sparse" output tensor
+ // (see L1038).
+ if (llvm::is_contained(tidLvls, tl))
+ return;
+ tidLvls.emplace_back(tl);
+ });
env.emitter().enterNewLoopSeq(builder, env.op().getLoc(), tidLvls);
@@ -1190,14 +1198,18 @@ static void genInitConstantDenseAddress(CodegenEnv &env,
static bool translateBitsToTidLvlPairs(
CodegenEnv &env, LatPointId li, LoopId curr,
SmallVectorImpl<TensorLevel> &tidLvls,
+ SmallVectorImpl<TensorLevel> &condTidLvls,
SmallVectorImpl<std::pair<TensorLevel, AffineExpr>> &affineTidLvls) {
- return getAllTidLvlsInLatPoints(env, li, curr,
- [&](TensorLevel tl, AffineExpr exp) {
- if (exp)
- affineTidLvls.emplace_back(tl, exp);
- else
- tidLvls.emplace_back(tl);
- });
+ return getAllTidLvlsInLatPoints(
+ env, li, curr, [&](TensorLevel tl, AffineExpr exp, bool isLoopCond) {
+ if (exp)
+ affineTidLvls.emplace_back(tl, exp);
+ else {
+ tidLvls.emplace_back(tl);
+ if (isLoopCond)
+ condTidLvls.emplace_back(tl);
+ }
+ });
}
/// Starts a single loop in current sequence.
@@ -1209,15 +1221,20 @@ static std::pair<Operation *, bool> startLoop(CodegenEnv &env,
// after fully migration.
// The set of tensors + lvls to generate loops on
SmallVector<TensorLevel> tidLvls;
+ SmallVector<TensorLevel> condTidLvls;
// The set of dense tensors with non-trivial affine expression that just
// becomes invariant and the address are generated at the current level.
SmallVector<std::pair<TensorLevel, AffineExpr>> affineTidLvls;
- bool isSingleCond =
- translateBitsToTidLvlPairs(env, li, curr, tidLvls, affineTidLvls);
+ bool isSingleCond = translateBitsToTidLvlPairs(env, li, curr, tidLvls,
+ condTidLvls, affineTidLvls);
// Emit the for/while-loop control.
- Operation *loop = genLoop(env, builder, curr, numCases, needsUniv, tidLvls);
+ ArrayRef<TensorLevel> loopTidLvls = env.generatingSparseIterator()
+ ? ArrayRef(condTidLvls)
+ : ArrayRef(tidLvls);
+ Operation *loop =
+ genLoop(env, builder, curr, numCases, needsUniv, loopTidLvls);
Location loc = env.op().getLoc();
for (auto [tidLvl, exp] : affineTidLvls) {
env.emitter().locateLvlAtAffineAddress(builder, loc, tidLvl, exp);
diff --git a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
index 4ca13514498905..0ad5ca575fd5cb 100644
--- a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
+++ b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
@@ -16,6 +16,10 @@
map = (d0) -> (d0 : compressed)
}>
+#CSR = #sparse_tensor.encoding<{
+ map = (d0, d1) -> (d0 : dense, d1 : compressed)
+}>
+
// CHECK-LABEL: func.func @sqsum(
// CHECK-DAG: %[[C0:.*]] = arith.constant 0 : index
@@ -167,3 +171,54 @@ func.func @add(%arg0: tensor<10xi32, #VEC>, %arg1: tensor<10xi32, #VEC>) -> tens
} -> tensor<10xi32>
return %0 : tensor<10xi32>
}
+
+// ITER-LABEL: func.func @matvec(
+// ITER: %[[VECTOR:.*]] = bufferization.to_buffer
+// ITER: %[[ROW_SPACE:.*]] = sparse_tensor.extract_iteration_space %{{.*}} lvls = 0
+// ITER: sparse_tensor.iterate %[[ROW_ITER:.*]] in %[[ROW_SPACE]] at(%[[ROW:[a-zA-Z0-9_]+]])
+// ITER: %[[COL_SPACE:.*]] = sparse_tensor.extract_iteration_space %{{.*}} at %[[ROW_ITER]] lvls = 1
+// ITER: sparse_tensor.iterate %[[COL_ITER:.*]] in %[[COL_SPACE]] at(%[[COL:[a-zA-Z0-9_]+]])
+// ITER: %[[MATRIX_VALUE:.*]] = sparse_tensor.extract_value %{{.*}} at %[[COL_ITER]]
+// ITER: %[[VECTOR_VALUE:.*]] = memref.load %[[VECTOR]][%[[COL]]]
+// ITER: %[[PRODUCT:.*]] = arith.mulf %[[MATRIX_VALUE]], %[[VECTOR_VALUE]] : f64
+// ITER: arith.addf %{{.*}}, %[[PRODUCT]] : f64
+
+// CHECK-LABEL: func.func @matvec(
+// CHECK: %[[C0:.*]] = arith.constant 0 : index
+// CHECK: %[[C1:.*]] = arith.constant 1 : index
+// CHECK: %[[VECTOR:.*]] = bufferization.to_buffer
+// CHECK: %[[OUTPUT:.*]] = bufferization.to_buffer
+// CHECK: scf.for %[[ROW:.*]] = %[[C0]] to %{{.*}} step %[[C1]] {
+// CHECK: %[[SUM:.*]] = memref.load %[[OUTPUT]][%[[ROW]]]
+// CHECK: %[[COL_LO:.*]] = memref.load %{{.*}}[%[[ROW]]]
+// CHECK: %[[ROW_END:.*]] = arith.addi %[[ROW]], %[[C1]] : index
+// CHECK: %[[COL_HI:.*]] = memref.load %{{.*}}[%[[ROW_END]]]
+// CHECK: %[[RESULT:.*]] = scf.for %[[POS:.*]] = %[[COL_LO]] to %[[COL_HI]] step %[[C1]] iter_args(%[[ACC:.*]] = %[[SUM]]) -> (f64) {
+// CHECK: %[[COL:.*]] = memref.load %{{.*}}[%[[POS]]]
+// CHECK: %[[MATRIX_VALUE:.*]] = memref.load %{{.*}}[%[[POS]]]
+// CHECK: %[[VECTOR_VALUE:.*]] = memref.load %[[VECTOR]][%[[COL]]]
+// CHECK: %[[PRODUCT:.*]] = arith.mulf %[[MATRIX_VALUE]], %[[VECTOR_VALUE]] : f64
+// CHECK: %[[NEXT:.*]] = arith.addf %[[ACC]], %[[PRODUCT]] : f64
+// CHECK: scf.yield %[[NEXT]] : f64
+// CHECK: }
+// CHECK: memref.store %[[RESULT]], %[[OUTPUT]][%[[ROW]]]
+// CHECK: }
+func.func @matvec(%matrix: tensor<4x8xf64, #CSR>,
+ %vector: tensor<8xf64>,
+ %output: tensor<4xf64>) -> tensor<4xf64> {
+ %result = linalg.generic {
+ indexing_maps = [
+ affine_map<(i, j) -> (i, j)>,
+ affine_map<(i, j) -> (j)>,
+ affine_map<(i, j) -> (i)>
+ ],
+ iterator_types = ["parallel", "reduction"]
+ } ins(%matrix, %vector : tensor<4x8xf64, #CSR>, tensor<8xf64>)
+ outs(%output : tensor<4xf64>) {
+ ^bb0(%matrixValue: f64, %vectorValue: f64, %sum: f64):
+ %product = arith.mulf %matrixValue, %vectorValue : f64
+ %next = arith.addf %sum, %product : f64
+ linalg.yield %next : f64
+ } -> tensor<4xf64>
+ return %result : tensor<4xf64>
+}
>From c8cde7c380de70ac1a2b307f4671a77939a8b1c7 Mon Sep 17 00:00:00 2001
From: Kohei Yamaguchi <fix7211 at gmail.com>
Date: Sun, 1 Nov 2026 00:48:12 +0900
Subject: [PATCH 2/4] Fix mixed sparse iterator loop handling
---
.../Transforms/Sparsification.cpp | 69 +++++------
.../Transforms/Utils/LoopEmitter.cpp | 107 +++++++++++++++---
.../Transforms/Utils/LoopEmitter.h | 9 +-
.../sparse_kernels_to_iterator.mlir | 41 +++++++
4 files changed, 165 insertions(+), 61 deletions(-)
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
index 9b307b31de8214..d4c662c4b9c72f 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
@@ -1008,7 +1008,7 @@ static void endIf(CodegenEnv &env, OpBuilder &builder, scf::IfOp ifOp,
static bool getAllTidLvlsInLatPoints(
CodegenEnv &env, LatPointId li, LoopId curr,
- llvm::function_ref<void(TensorLevel, AffineExpr, bool)> callback) {
+ llvm::function_ref<void(TensorLevel, AffineExpr)> callback) {
const BitVector &simple = env.lat(li).simple;
const TensorId outTid = env.merger().getOutTensorID();
const std::optional<Level> outLvl = env.merger().getLvl(outTid, curr);
@@ -1020,8 +1020,7 @@ static bool getAllTidLvlsInLatPoints(
LevelType lt, bool isIdxReduc) {
if (simple[b]) {
if (isIdxReduc) {
- callback(env.makeTensorLevel(tid, *lvl), nullptr,
- /*isLoopCond=*/true);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr);
numloopCond++;
return;
}
@@ -1045,12 +1044,10 @@ static bool getAllTidLvlsInLatPoints(
}
}
hasNonUnique = !isUniqueLT(lt) || hasNonUnique;
- callback(env.makeTensorLevel(tid, *lvl), nullptr,
- /*isLoopCond=*/true);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr);
numloopCond++;
} else if (lt.hasDenseSemantic() || isIdxReduc) {
- callback(env.makeTensorLevel(tid, *lvl), nullptr,
- /*isLoopCond=*/false);
+ callback(env.makeTensorLevel(tid, *lvl), nullptr);
} else {
assert(isUndefLT(lt));
linalg::GenericOp op = env.op();
@@ -1085,8 +1082,7 @@ static bool getAllTidLvlsInLatPoints(
// level. We need to generate the address according to the
// affine expression. This is also the best place we can do it
// to avoid putting it inside inner loops.
- callback(env.makeTensorLevel(tid, l), exp,
- /*isLoopCond=*/false);
+ callback(env.makeTensorLevel(tid, l), exp);
}
}
}
@@ -1101,16 +1097,14 @@ static bool getAllTidLvlsInLatPoints(
// TODO: we should avoid introducing corner cases for all-dense sparse
// tensors.
if (stt.hasEncoding() && stt.isAllDense())
- callback(env.makeTensorLevel(outTid, *outLvl), nullptr,
- /*isLoopCond=*/false);
+ callback(env.makeTensorLevel(outTid, *outLvl), nullptr);
}
if (numloopCond == 0) {
// Corner cases where the loop bound is defined by a *unused* operand, in
// this case, we just generate a dense "fake" loop by iterating over the
// synthetic tensor.
- callback(env.makeTensorLevel(env.merger().getSynTensorID(), curr), nullptr,
- /*isLoopCond=*/true);
+ callback(env.makeTensorLevel(env.merger().getSynTensorID(), curr), nullptr);
numloopCond++;
}
// If we just need to one loop conditions and the conditions is not imposed on
@@ -1135,16 +1129,14 @@ static bool startLoopSeq(CodegenEnv &env, OpBuilder &builder, ExprId exp,
const LatPointId l0 = env.set(lts)[0];
SmallVector<TensorLevel> tidLvls;
- getAllTidLvlsInLatPoints(env, l0, curr,
- [&](TensorLevel tl, AffineExpr, bool) {
- // TODO: remove this! The same tensor level might
- // be added for multiple times due to the special
- // handling for all-dense "sparse" output tensor
- // (see L1038).
- if (llvm::is_contained(tidLvls, tl))
- return;
- tidLvls.emplace_back(tl);
- });
+ getAllTidLvlsInLatPoints(env, l0, curr, [&](TensorLevel tl, AffineExpr) {
+ // TODO: remove this! The same tensor level might be added for multiple
+ // times due to the special handling for all-dense "sparse" output tensor
+ // (see L1038).
+ if (llvm::is_contained(tidLvls, tl))
+ return;
+ tidLvls.emplace_back(tl);
+ });
env.emitter().enterNewLoopSeq(builder, env.op().getLoc(), tidLvls);
@@ -1198,18 +1190,14 @@ static void genInitConstantDenseAddress(CodegenEnv &env,
static bool translateBitsToTidLvlPairs(
CodegenEnv &env, LatPointId li, LoopId curr,
SmallVectorImpl<TensorLevel> &tidLvls,
- SmallVectorImpl<TensorLevel> &condTidLvls,
SmallVectorImpl<std::pair<TensorLevel, AffineExpr>> &affineTidLvls) {
- return getAllTidLvlsInLatPoints(
- env, li, curr, [&](TensorLevel tl, AffineExpr exp, bool isLoopCond) {
- if (exp)
- affineTidLvls.emplace_back(tl, exp);
- else {
- tidLvls.emplace_back(tl);
- if (isLoopCond)
- condTidLvls.emplace_back(tl);
- }
- });
+ return getAllTidLvlsInLatPoints(env, li, curr,
+ [&](TensorLevel tl, AffineExpr exp) {
+ if (exp)
+ affineTidLvls.emplace_back(tl, exp);
+ else
+ tidLvls.emplace_back(tl);
+ });
}
/// Starts a single loop in current sequence.
@@ -1221,20 +1209,15 @@ static std::pair<Operation *, bool> startLoop(CodegenEnv &env,
// after fully migration.
// The set of tensors + lvls to generate loops on
SmallVector<TensorLevel> tidLvls;
- SmallVector<TensorLevel> condTidLvls;
// The set of dense tensors with non-trivial affine expression that just
// becomes invariant and the address are generated at the current level.
SmallVector<std::pair<TensorLevel, AffineExpr>> affineTidLvls;
- bool isSingleCond = translateBitsToTidLvlPairs(env, li, curr, tidLvls,
- condTidLvls, affineTidLvls);
+ bool isSingleCond =
+ translateBitsToTidLvlPairs(env, li, curr, tidLvls, affineTidLvls);
// Emit the for/while-loop control.
- ArrayRef<TensorLevel> loopTidLvls = env.generatingSparseIterator()
- ? ArrayRef(condTidLvls)
- : ArrayRef(tidLvls);
- Operation *loop =
- genLoop(env, builder, curr, numCases, needsUniv, loopTidLvls);
+ Operation *loop = genLoop(env, builder, curr, numCases, needsUniv, tidLvls);
Location loc = env.op().getLoc();
for (auto [tidLvl, exp] : affineTidLvls) {
env.emitter().locateLvlAtAffineAddress(builder, loc, tidLvl, exp);
@@ -1315,7 +1298,7 @@ static void genStmt(CodegenEnv &env, RewriterBase &rewriter, ExprId exp,
const LatPointId li = env.set(lts)[0];
auto [loop, isSingleCond] =
startLoop(env, rewriter, curr, li, lsize, needsUniv);
- assert(isSingleCond == llvm::isa<IterateOp>(loop));
+ isSingleCond = llvm::isa<IterateOp, scf::ForOp>(loop);
// We cannot change this to `for (const LatPointId li : env.set(lts))`
// because the loop body causes data-movement which invalidates
// the iterator.
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
index 94d6e640da4367..1746adb90927f9 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
@@ -275,22 +275,24 @@ void LoopEmitter::initializeLoopEmit(
}
}
- // The sparse iterator values will only be available after the loop is
- // constructed.
- if (emitStrategy == SparseEmitStrategy::kSparseIterator)
- return;
-
// For every synthetic tensor, set the high bound by calling the callback.
if (synSetter) {
TensorId synId = getSynTensorId();
for (unsigned i = 0, e = loopHighs.size(); i < e; i++) {
Value sz = loopHighs[i] = synSetter(builder, loc, i);
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator)
+ continue;
auto [stl, it] = makeSynLevelAndIterator(sz, synId, i, emitStrategy);
lvls[synId][i] = std::move(stl);
iters[synId][i].emplace_back(std::move(it));
}
}
+ // The sparse iterator values will only be available after the loop is
+ // constructed.
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator)
+ return;
+
// For every manifest tensor:
// * For every level:
// * get the positions and coordinates buffers
@@ -536,6 +538,38 @@ bool LoopEmitter::shouldIteratedByForLoop(ArrayRef<SparseIterator *> spIters) {
return true;
}
+void LoopEmitter::linkSparseIteratorDenseLvls(OpBuilder &builder, Location loc,
+ ArrayRef<TensorLevel> tidLvls,
+ Value crd) {
+ // Dense levels do not drive coiteration, but their iterator values are
+ // needed when extracting a child iteration space.
+ for (auto [tid, lvl] : unpackTensorLevelRange(tidLvls)) {
+ SparseTensorType stt = getSparseTensorType(tensors[tid]);
+ assert(stt.getLvlType(lvl).hasDenseSemantic());
+
+ Value pos = crd;
+ if (lvl != 0) {
+ Value parentIter = spIterVals[tid][lvl - 1];
+ assert(parentIter && "expected parent iterator for encoded dense level");
+ IteratorType parentTp = cast<IteratorType>(parentIter.getType());
+ SmallVector<Type> cursorTps(parentTp.isUnique() ? 1 : 2,
+ builder.getIndexType());
+ auto parentCursor = UnrealizedConversionCastOp::create(
+ builder, loc, cursorTps, parentIter);
+ Value parentPos = parentCursor.getResults().back();
+ Value lvlSize = LvlOp::create(builder, loc, tensors[tid], lvl);
+ pos = ADDI(MULI(parentPos, lvlSize), crd);
+ }
+
+ IteratorType itTp = IterSpaceType::get(builder.getContext(),
+ stt.getEncoding(), lvl, lvl + 1)
+ .getIteratorType();
+ spIterVals[tid][lvl] =
+ UnrealizedConversionCastOp::create(builder, loc, itTp, pos)
+ .getResult(0);
+ }
+}
+
Region *LoopEmitter::enterCurrentCoIterationCase(OpBuilder &builder,
Location loc,
I64BitSet caseBit,
@@ -586,6 +620,9 @@ Region *LoopEmitter::enterCurrentCoIterationCase(OpBuilder &builder,
}
// Must have consumed all iterator SSA values.
assert(iters.empty());
+ linkSparseIteratorDenseLvls(builder, loc,
+ loopStack.back().auxiliaryDenseTidLvls,
+ loopStack.back().iv);
return &caseRegion;
}
@@ -595,10 +632,43 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
bool needsUniv) {
// TODO: Argument `numCases` only used when generating iterator-based sparse
// loops. Simplify the code upon feature complete.
- // TODO: handle coiteration with sparse iterator.
if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
- if (tidLvls.size() == 1) {
- auto [tid, lvl] = unpackTensorLevel(tidLvls.front());
+ SmallVector<TensorLevel> iteratedTidLvls;
+ SmallVector<TensorLevel> auxiliaryDenseTidLvls;
+ for (auto [tidLvl, tidAndLvl] :
+ llvm::zip_equal(tidLvls, unpackTensorLevelRange(tidLvls))) {
+ auto [tid, lvl] = tidAndLvl;
+ if (isSynTensor(tid))
+ continue;
+ SparseTensorType stt = getSparseTensorType(tensors[tid]);
+ if (!stt.hasEncoding())
+ continue;
+ if (stt.getLvlType(lvl).hasDenseSemantic())
+ auxiliaryDenseTidLvls.push_back(tidLvl);
+ else
+ iteratedTidLvls.push_back(tidLvl);
+ }
+
+ if (iteratedTidLvls.empty() && !auxiliaryDenseTidLvls.empty()) {
+ iteratedTidLvls.push_back(auxiliaryDenseTidLvls.front());
+ auxiliaryDenseTidLvls.erase(auxiliaryDenseTidLvls.begin());
+ }
+
+ // Unencoded dense and synthetic levels are represented by an SCF loop.
+ if (iteratedTidLvls.empty()) {
+ Value hi = loopHighs[loopStack.size()];
+ assert(hi && "expected a loop bound for dense iteration");
+ scf::ForOp forOp =
+ scf::ForOp::create(builder, loc, C_IDX(0), hi, C_IDX(1), reduc);
+ llvm::copy(forOp.getRegionIterArgs(), reduc.begin());
+ builder.setInsertionPointToStart(forOp.getBody());
+ loopStack.emplace_back(tidLvls, forOp, builder.getInsertionBlock(),
+ forOp.getInductionVar(), loopTag);
+ return forOp;
+ }
+
+ if (iteratedTidLvls.size() == 1) {
+ auto [tid, lvl] = unpackTensorLevel(iteratedTidLvls.front());
Value t = tensors[tid];
// Extract and iterate over the iteration space.
@@ -615,14 +685,17 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
llvm::copy(iterOp.getRegionIterArgs(), reduc.begin());
// Set the insertion point to loop body.
builder.setInsertionPointToStart(iterOp.getBody());
- loopStack.emplace_back(tidLvls, iterOp, builder.getInsertionBlock(),
- iterOp.getCrds().front(), loopTag);
+ linkSparseIteratorDenseLvls(builder, loc, auxiliaryDenseTidLvls,
+ iterOp.getCrds().front());
+ loopStack.emplace_back(
+ iteratedTidLvls, iterOp, builder.getInsertionBlock(),
+ iterOp.getCrds().front(), loopTag, auxiliaryDenseTidLvls);
return iterOp;
}
// CoIteration Loops.
SmallVector<Value> spaces;
- for (auto [tid, lvl] : unpackTensorLevelRange(tidLvls)) {
+ for (auto [tid, lvl] : unpackTensorLevelRange(iteratedTidLvls)) {
Value t = tensors[tid];
ExtractIterSpaceOp extractSpaceOp =
lvl == 0 ? ExtractIterSpaceOp::create(builder, loc, t)
@@ -633,8 +706,10 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
auto coIterOp = CoIterateOp::create(builder, loc, spaces, reduc, numCases);
// The CoIterationOp does not have insertion block nor induction variable.
// TODO: the `struct LoopInfo` should be simplied after full migration.
- loopStack.emplace_back(tidLvls, coIterOp, /*insertion block*/ nullptr,
- /*induction variable*/ nullptr, loopTag);
+ loopStack.emplace_back(iteratedTidLvls, coIterOp,
+ /*insertion block*/ nullptr,
+ /*induction variable*/ nullptr, loopTag,
+ auxiliaryDenseTidLvls);
return coIterOp;
}
@@ -725,8 +800,7 @@ void LoopEmitter::prepareLoopOverTensorAtLvl(OpBuilder &builder, Location loc,
void LoopEmitter::exitForLoop(RewriterBase &rewriter, Location loc,
MutableArrayRef<Value> reduc) {
const LoopInfo &loopInfo = loopStack.back();
- if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
- auto iterateOp = llvm::cast<IterateOp>(loopInfo.loop);
+ if (auto iterateOp = llvm::dyn_cast<IterateOp>(loopInfo.loop)) {
assert(reduc.size() == iterateOp.getNumResults());
sparse_tensor::YieldOp::create(rewriter, loc, reduc);
// Exit the loop.
@@ -869,7 +943,8 @@ void LoopEmitter::exitCurrentLoop(RewriterBase &rewriter, Location loc,
// Clean up the values, it would help use to discover potential bug at a
// earlier stage (instead of silently using a wrong value).
const LoopInfo &loopInfo = loopStack.back();
- if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator &&
+ !isa<scf::ForOp>(loopInfo.loop)) {
Operation *p = loopInfo.loop;
if (isa<IterateOp>(p))
sparse_tensor::YieldOp::create(rewriter, loc, reduc);
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
index a47062987b4b6c..879dc03b057e21 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
@@ -253,8 +253,10 @@ class LoopEmitter {
// the set of tensors levels that the loop is iterating over.
struct LoopInfo final {
LoopInfo(ArrayRef<TensorLevel> tidLvls, Operation *loop, Block *userBlock,
- Value iv, StringAttr loopTag)
- : tidLvls(tidLvls), loop(loop), userCodeBlock(userBlock), iv(iv) {
+ Value iv, StringAttr loopTag,
+ ArrayRef<TensorLevel> auxiliaryDenseTidLvls = {})
+ : tidLvls(tidLvls), auxiliaryDenseTidLvls(auxiliaryDenseTidLvls),
+ loop(loop), userCodeBlock(userBlock), iv(iv) {
// Attached a special tag to loop emitter generated loop.
if (loopTag)
loop->setDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName(),
@@ -265,6 +267,7 @@ class LoopEmitter {
// required for levels with non-tivial index expressions, which is
// maintained by the sliceDrivenInfo array below.
const llvm::SmallVector<TensorLevel> tidLvls;
+ const llvm::SmallVector<TensorLevel> auxiliaryDenseTidLvls;
Operation *loop; // the loop operation
Block *const userCodeBlock; // the block holding users' generated code.
Value iv; // the induction variable for the loop
@@ -273,6 +276,8 @@ class LoopEmitter {
void categorizeIterators(ArrayRef<TensorLevel> tidLvls,
SmallVectorImpl<SparseIterator *> &raIters,
SmallVectorImpl<SparseIterator *> &spIters);
+ void linkSparseIteratorDenseLvls(OpBuilder &builder, Location loc,
+ ArrayRef<TensorLevel> tidLvls, Value crd);
///
/// LoopEmitter internal helper functions.
///
diff --git a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
index 0ad5ca575fd5cb..8585b9a4a2f06e 100644
--- a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
+++ b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
@@ -222,3 +222,44 @@ func.func @matvec(%matrix: tensor<4x8xf64, #CSR>,
} -> tensor<4xf64>
return %result : tensor<4xf64>
}
+
+// ITER-LABEL: func.func @mixed_sparse_encoded_dense_parent(
+// ITER: sparse_tensor.iterate %{{.*}} in %{{.*}} at(%[[ROW:.*]])
+// ITER: %[[DENSE_ITER:.*]] = builtin.unrealized_conversion_cast %[[ROW]]
+// ITER: sparse_tensor.extract_iteration_space %{{.*}} at %[[DENSE_ITER]] lvls = 1
+func.func @mixed_sparse_encoded_dense_parent(
+ %rows: tensor<4xf64, #VEC>, %matrix: tensor<4x8xf64, #CSR>,
+ %output: tensor<4x8xf64>) -> tensor<4x8xf64> {
+ %result = linalg.generic {
+ indexing_maps = [
+ affine_map<(i, j) -> (i)>,
+ affine_map<(i, j) -> (i, j)>,
+ affine_map<(i, j) -> (i, j)>
+ ],
+ iterator_types = ["parallel", "parallel"]
+ } ins(%rows, %matrix : tensor<4xf64, #VEC>, tensor<4x8xf64, #CSR>)
+ outs(%output : tensor<4x8xf64>) {
+ ^bb0(%rowValue: f64, %matrixValue: f64, %out: f64):
+ %product = arith.mulf %rowValue, %matrixValue : f64
+ linalg.yield %product : f64
+ } -> tensor<4x8xf64>
+ return %result : tensor<4x8xf64>
+}
+
+// ITER-LABEL: func.func @sparse_broadcast(
+// ITER: sparse_tensor.iterate
+// ITER: scf.for
+func.func @sparse_broadcast(%input: tensor<4xf64, #VEC>,
+ %output: tensor<4x8xf64>) -> tensor<4x8xf64> {
+ %result = linalg.generic {
+ indexing_maps = [
+ affine_map<(i, j) -> (i)>,
+ affine_map<(i, j) -> (i, j)>
+ ],
+ iterator_types = ["parallel", "parallel"]
+ } ins(%input : tensor<4xf64, #VEC>) outs(%output : tensor<4x8xf64>) {
+ ^bb0(%inputValue: f64, %outputValue: f64):
+ linalg.yield %inputValue : f64
+ } -> tensor<4x8xf64>
+ return %result : tensor<4x8xf64>
+}
>From ea07ef9187372712e2da88cff13f52714f0f324e Mon Sep 17 00:00:00 2001
From: Kohei Yamaguchi <fix7211 at gmail.com>
Date: Sun, 1 Nov 2026 02:19:18 +0900
Subject: [PATCH 3/4] Test sparse iterator SCF lowering
---
.../sparse_kernels_to_iterator.mlir | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
index 8585b9a4a2f06e..df2776d65f4663 100644
--- a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
+++ b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
@@ -227,6 +227,17 @@ func.func @matvec(%matrix: tensor<4x8xf64, #CSR>,
// ITER: sparse_tensor.iterate %{{.*}} in %{{.*}} at(%[[ROW:.*]])
// ITER: %[[DENSE_ITER:.*]] = builtin.unrealized_conversion_cast %[[ROW]]
// ITER: sparse_tensor.extract_iteration_space %{{.*}} at %[[DENSE_ITER]] lvls = 1
+
+// CHECK-LABEL: func.func @mixed_sparse_encoded_dense_parent(
+// CHECK: %[[MIXED_C1:.*]] = arith.constant 1 : index
+// CHECK: scf.for %[[MIXED_ROW_POS:.*]] = %{{.*}} to %{{.*}} step %[[MIXED_C1]] {
+// CHECK: %[[MIXED_ROW:.*]] = memref.load %{{.*}}[%[[MIXED_ROW_POS]]]
+// CHECK: %[[MIXED_COL_LO:.*]] = memref.load %{{.*}}[%[[MIXED_ROW]]]
+// CHECK: %[[MIXED_ROW_END:.*]] = arith.addi %[[MIXED_ROW]], %[[MIXED_C1]] : index
+// CHECK: %[[MIXED_COL_HI:.*]] = memref.load %{{.*}}[%[[MIXED_ROW_END]]]
+// CHECK: scf.for %[[MIXED_COL_POS:.*]] = %[[MIXED_COL_LO]] to %[[MIXED_COL_HI]] step %[[MIXED_C1]] {
+// CHECK: %[[MIXED_COL:.*]] = memref.load %{{.*}}[%[[MIXED_COL_POS]]]
+// CHECK: memref.store %{{.*}}, %{{.*}}[%[[MIXED_ROW]], %[[MIXED_COL]]]
func.func @mixed_sparse_encoded_dense_parent(
%rows: tensor<4xf64, #VEC>, %matrix: tensor<4x8xf64, #CSR>,
%output: tensor<4x8xf64>) -> tensor<4x8xf64> {
@@ -249,6 +260,15 @@ func.func @mixed_sparse_encoded_dense_parent(
// ITER-LABEL: func.func @sparse_broadcast(
// ITER: sparse_tensor.iterate
// ITER: scf.for
+
+// CHECK-LABEL: func.func @sparse_broadcast(
+// CHECK: %[[BROADCAST_C0:.*]] = arith.constant 0 : index
+// CHECK: %[[BROADCAST_C8:.*]] = arith.constant 8 : index
+// CHECK: scf.for %[[BROADCAST_ROW_POS:.*]] = %{{.*}} to %{{.*}} step %{{.*}} {
+// CHECK: %[[BROADCAST_ROW:.*]] = memref.load %{{.*}}[%[[BROADCAST_ROW_POS]]]
+// CHECK: %[[BROADCAST_VALUE:.*]] = memref.load %{{.*}}[%[[BROADCAST_ROW_POS]]]
+// CHECK: scf.for %[[BROADCAST_COL:.*]] = %[[BROADCAST_C0]] to %[[BROADCAST_C8]] step %{{.*}} {
+// CHECK: memref.store %[[BROADCAST_VALUE]], %{{.*}}[%[[BROADCAST_ROW]], %[[BROADCAST_COL]]]
func.func @sparse_broadcast(%input: tensor<4xf64, #VEC>,
%output: tensor<4x8xf64>) -> tensor<4x8xf64> {
%result = linalg.generic {
>From 7416c9271e438fe563867a1e17f05f7fc3446377 Mon Sep 17 00:00:00 2001
From: Kohei Yamaguchi <fix7211 at gmail.com>
Date: Tue, 3 Nov 2026 15:13:26 +0900
Subject: [PATCH 4/4] Simplify sparse iterator loop selection
---
.../Transforms/Sparsification.cpp | 2 +-
.../Transforms/Utils/LoopEmitter.cpp | 106 ++++--------------
.../Transforms/Utils/LoopEmitter.h | 9 +-
.../sparse_kernels_to_iterator.mlir | 61 ----------
4 files changed, 23 insertions(+), 155 deletions(-)
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
index d4c662c4b9c72f..860ea68029c3a4 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
@@ -1298,7 +1298,7 @@ static void genStmt(CodegenEnv &env, RewriterBase &rewriter, ExprId exp,
const LatPointId li = env.set(lts)[0];
auto [loop, isSingleCond] =
startLoop(env, rewriter, curr, li, lsize, needsUniv);
- isSingleCond = llvm::isa<IterateOp, scf::ForOp>(loop);
+ assert(isSingleCond == llvm::isa<IterateOp>(loop));
// We cannot change this to `for (const LatPointId li : env.set(lts))`
// because the loop body causes data-movement which invalidates
// the iterator.
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
index 1746adb90927f9..56d8b0438419b0 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.cpp
@@ -275,24 +275,22 @@ void LoopEmitter::initializeLoopEmit(
}
}
+ // The sparse iterator values will only be available after the loop is
+ // constructed.
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator)
+ return;
+
// For every synthetic tensor, set the high bound by calling the callback.
if (synSetter) {
TensorId synId = getSynTensorId();
for (unsigned i = 0, e = loopHighs.size(); i < e; i++) {
Value sz = loopHighs[i] = synSetter(builder, loc, i);
- if (emitStrategy == SparseEmitStrategy::kSparseIterator)
- continue;
auto [stl, it] = makeSynLevelAndIterator(sz, synId, i, emitStrategy);
lvls[synId][i] = std::move(stl);
iters[synId][i].emplace_back(std::move(it));
}
}
- // The sparse iterator values will only be available after the loop is
- // constructed.
- if (emitStrategy == SparseEmitStrategy::kSparseIterator)
- return;
-
// For every manifest tensor:
// * For every level:
// * get the positions and coordinates buffers
@@ -538,38 +536,6 @@ bool LoopEmitter::shouldIteratedByForLoop(ArrayRef<SparseIterator *> spIters) {
return true;
}
-void LoopEmitter::linkSparseIteratorDenseLvls(OpBuilder &builder, Location loc,
- ArrayRef<TensorLevel> tidLvls,
- Value crd) {
- // Dense levels do not drive coiteration, but their iterator values are
- // needed when extracting a child iteration space.
- for (auto [tid, lvl] : unpackTensorLevelRange(tidLvls)) {
- SparseTensorType stt = getSparseTensorType(tensors[tid]);
- assert(stt.getLvlType(lvl).hasDenseSemantic());
-
- Value pos = crd;
- if (lvl != 0) {
- Value parentIter = spIterVals[tid][lvl - 1];
- assert(parentIter && "expected parent iterator for encoded dense level");
- IteratorType parentTp = cast<IteratorType>(parentIter.getType());
- SmallVector<Type> cursorTps(parentTp.isUnique() ? 1 : 2,
- builder.getIndexType());
- auto parentCursor = UnrealizedConversionCastOp::create(
- builder, loc, cursorTps, parentIter);
- Value parentPos = parentCursor.getResults().back();
- Value lvlSize = LvlOp::create(builder, loc, tensors[tid], lvl);
- pos = ADDI(MULI(parentPos, lvlSize), crd);
- }
-
- IteratorType itTp = IterSpaceType::get(builder.getContext(),
- stt.getEncoding(), lvl, lvl + 1)
- .getIteratorType();
- spIterVals[tid][lvl] =
- UnrealizedConversionCastOp::create(builder, loc, itTp, pos)
- .getResult(0);
- }
-}
-
Region *LoopEmitter::enterCurrentCoIterationCase(OpBuilder &builder,
Location loc,
I64BitSet caseBit,
@@ -620,9 +586,6 @@ Region *LoopEmitter::enterCurrentCoIterationCase(OpBuilder &builder,
}
// Must have consumed all iterator SSA values.
assert(iters.empty());
- linkSparseIteratorDenseLvls(builder, loc,
- loopStack.back().auxiliaryDenseTidLvls,
- loopStack.back().iv);
return &caseRegion;
}
@@ -633,42 +596,18 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
// TODO: Argument `numCases` only used when generating iterator-based sparse
// loops. Simplify the code upon feature complete.
if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
- SmallVector<TensorLevel> iteratedTidLvls;
- SmallVector<TensorLevel> auxiliaryDenseTidLvls;
- for (auto [tidLvl, tidAndLvl] :
- llvm::zip_equal(tidLvls, unpackTensorLevelRange(tidLvls))) {
- auto [tid, lvl] = tidAndLvl;
+ SmallVector<TensorLevel> encodedTidLvls;
+ for (TensorLevel tidLvl : tidLvls) {
+ TensorId tid = unpackTensorLevel(tidLvl).first;
if (isSynTensor(tid))
continue;
SparseTensorType stt = getSparseTensorType(tensors[tid]);
- if (!stt.hasEncoding())
- continue;
- if (stt.getLvlType(lvl).hasDenseSemantic())
- auxiliaryDenseTidLvls.push_back(tidLvl);
- else
- iteratedTidLvls.push_back(tidLvl);
- }
-
- if (iteratedTidLvls.empty() && !auxiliaryDenseTidLvls.empty()) {
- iteratedTidLvls.push_back(auxiliaryDenseTidLvls.front());
- auxiliaryDenseTidLvls.erase(auxiliaryDenseTidLvls.begin());
- }
-
- // Unencoded dense and synthetic levels are represented by an SCF loop.
- if (iteratedTidLvls.empty()) {
- Value hi = loopHighs[loopStack.size()];
- assert(hi && "expected a loop bound for dense iteration");
- scf::ForOp forOp =
- scf::ForOp::create(builder, loc, C_IDX(0), hi, C_IDX(1), reduc);
- llvm::copy(forOp.getRegionIterArgs(), reduc.begin());
- builder.setInsertionPointToStart(forOp.getBody());
- loopStack.emplace_back(tidLvls, forOp, builder.getInsertionBlock(),
- forOp.getInductionVar(), loopTag);
- return forOp;
+ if (stt.hasEncoding())
+ encodedTidLvls.push_back(tidLvl);
}
- if (iteratedTidLvls.size() == 1) {
- auto [tid, lvl] = unpackTensorLevel(iteratedTidLvls.front());
+ if (encodedTidLvls.size() == 1) {
+ auto [tid, lvl] = unpackTensorLevel(encodedTidLvls.front());
Value t = tensors[tid];
// Extract and iterate over the iteration space.
@@ -685,17 +624,14 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
llvm::copy(iterOp.getRegionIterArgs(), reduc.begin());
// Set the insertion point to loop body.
builder.setInsertionPointToStart(iterOp.getBody());
- linkSparseIteratorDenseLvls(builder, loc, auxiliaryDenseTidLvls,
- iterOp.getCrds().front());
- loopStack.emplace_back(
- iteratedTidLvls, iterOp, builder.getInsertionBlock(),
- iterOp.getCrds().front(), loopTag, auxiliaryDenseTidLvls);
+ loopStack.emplace_back(tidLvls, iterOp, builder.getInsertionBlock(),
+ iterOp.getCrds().front(), loopTag);
return iterOp;
}
// CoIteration Loops.
SmallVector<Value> spaces;
- for (auto [tid, lvl] : unpackTensorLevelRange(iteratedTidLvls)) {
+ for (auto [tid, lvl] : unpackTensorLevelRange(tidLvls)) {
Value t = tensors[tid];
ExtractIterSpaceOp extractSpaceOp =
lvl == 0 ? ExtractIterSpaceOp::create(builder, loc, t)
@@ -706,10 +642,8 @@ Operation *LoopEmitter::enterCoIterationOverTensorsAtLvls(
auto coIterOp = CoIterateOp::create(builder, loc, spaces, reduc, numCases);
// The CoIterationOp does not have insertion block nor induction variable.
// TODO: the `struct LoopInfo` should be simplied after full migration.
- loopStack.emplace_back(iteratedTidLvls, coIterOp,
- /*insertion block*/ nullptr,
- /*induction variable*/ nullptr, loopTag,
- auxiliaryDenseTidLvls);
+ loopStack.emplace_back(tidLvls, coIterOp, /*insertion block*/ nullptr,
+ /*induction variable*/ nullptr, loopTag);
return coIterOp;
}
@@ -800,7 +734,8 @@ void LoopEmitter::prepareLoopOverTensorAtLvl(OpBuilder &builder, Location loc,
void LoopEmitter::exitForLoop(RewriterBase &rewriter, Location loc,
MutableArrayRef<Value> reduc) {
const LoopInfo &loopInfo = loopStack.back();
- if (auto iterateOp = llvm::dyn_cast<IterateOp>(loopInfo.loop)) {
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
+ auto iterateOp = llvm::cast<IterateOp>(loopInfo.loop);
assert(reduc.size() == iterateOp.getNumResults());
sparse_tensor::YieldOp::create(rewriter, loc, reduc);
// Exit the loop.
@@ -943,8 +878,7 @@ void LoopEmitter::exitCurrentLoop(RewriterBase &rewriter, Location loc,
// Clean up the values, it would help use to discover potential bug at a
// earlier stage (instead of silently using a wrong value).
const LoopInfo &loopInfo = loopStack.back();
- if (emitStrategy == SparseEmitStrategy::kSparseIterator &&
- !isa<scf::ForOp>(loopInfo.loop)) {
+ if (emitStrategy == SparseEmitStrategy::kSparseIterator) {
Operation *p = loopInfo.loop;
if (isa<IterateOp>(p))
sparse_tensor::YieldOp::create(rewriter, loc, reduc);
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
index 879dc03b057e21..a47062987b4b6c 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
@@ -253,10 +253,8 @@ class LoopEmitter {
// the set of tensors levels that the loop is iterating over.
struct LoopInfo final {
LoopInfo(ArrayRef<TensorLevel> tidLvls, Operation *loop, Block *userBlock,
- Value iv, StringAttr loopTag,
- ArrayRef<TensorLevel> auxiliaryDenseTidLvls = {})
- : tidLvls(tidLvls), auxiliaryDenseTidLvls(auxiliaryDenseTidLvls),
- loop(loop), userCodeBlock(userBlock), iv(iv) {
+ Value iv, StringAttr loopTag)
+ : tidLvls(tidLvls), loop(loop), userCodeBlock(userBlock), iv(iv) {
// Attached a special tag to loop emitter generated loop.
if (loopTag)
loop->setDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName(),
@@ -267,7 +265,6 @@ class LoopEmitter {
// required for levels with non-tivial index expressions, which is
// maintained by the sliceDrivenInfo array below.
const llvm::SmallVector<TensorLevel> tidLvls;
- const llvm::SmallVector<TensorLevel> auxiliaryDenseTidLvls;
Operation *loop; // the loop operation
Block *const userCodeBlock; // the block holding users' generated code.
Value iv; // the induction variable for the loop
@@ -276,8 +273,6 @@ class LoopEmitter {
void categorizeIterators(ArrayRef<TensorLevel> tidLvls,
SmallVectorImpl<SparseIterator *> &raIters,
SmallVectorImpl<SparseIterator *> &spIters);
- void linkSparseIteratorDenseLvls(OpBuilder &builder, Location loc,
- ArrayRef<TensorLevel> tidLvls, Value crd);
///
/// LoopEmitter internal helper functions.
///
diff --git a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
index df2776d65f4663..0ad5ca575fd5cb 100644
--- a/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
+++ b/mlir/test/Dialect/SparseTensor/sparse_kernels_to_iterator.mlir
@@ -222,64 +222,3 @@ func.func @matvec(%matrix: tensor<4x8xf64, #CSR>,
} -> tensor<4xf64>
return %result : tensor<4xf64>
}
-
-// ITER-LABEL: func.func @mixed_sparse_encoded_dense_parent(
-// ITER: sparse_tensor.iterate %{{.*}} in %{{.*}} at(%[[ROW:.*]])
-// ITER: %[[DENSE_ITER:.*]] = builtin.unrealized_conversion_cast %[[ROW]]
-// ITER: sparse_tensor.extract_iteration_space %{{.*}} at %[[DENSE_ITER]] lvls = 1
-
-// CHECK-LABEL: func.func @mixed_sparse_encoded_dense_parent(
-// CHECK: %[[MIXED_C1:.*]] = arith.constant 1 : index
-// CHECK: scf.for %[[MIXED_ROW_POS:.*]] = %{{.*}} to %{{.*}} step %[[MIXED_C1]] {
-// CHECK: %[[MIXED_ROW:.*]] = memref.load %{{.*}}[%[[MIXED_ROW_POS]]]
-// CHECK: %[[MIXED_COL_LO:.*]] = memref.load %{{.*}}[%[[MIXED_ROW]]]
-// CHECK: %[[MIXED_ROW_END:.*]] = arith.addi %[[MIXED_ROW]], %[[MIXED_C1]] : index
-// CHECK: %[[MIXED_COL_HI:.*]] = memref.load %{{.*}}[%[[MIXED_ROW_END]]]
-// CHECK: scf.for %[[MIXED_COL_POS:.*]] = %[[MIXED_COL_LO]] to %[[MIXED_COL_HI]] step %[[MIXED_C1]] {
-// CHECK: %[[MIXED_COL:.*]] = memref.load %{{.*}}[%[[MIXED_COL_POS]]]
-// CHECK: memref.store %{{.*}}, %{{.*}}[%[[MIXED_ROW]], %[[MIXED_COL]]]
-func.func @mixed_sparse_encoded_dense_parent(
- %rows: tensor<4xf64, #VEC>, %matrix: tensor<4x8xf64, #CSR>,
- %output: tensor<4x8xf64>) -> tensor<4x8xf64> {
- %result = linalg.generic {
- indexing_maps = [
- affine_map<(i, j) -> (i)>,
- affine_map<(i, j) -> (i, j)>,
- affine_map<(i, j) -> (i, j)>
- ],
- iterator_types = ["parallel", "parallel"]
- } ins(%rows, %matrix : tensor<4xf64, #VEC>, tensor<4x8xf64, #CSR>)
- outs(%output : tensor<4x8xf64>) {
- ^bb0(%rowValue: f64, %matrixValue: f64, %out: f64):
- %product = arith.mulf %rowValue, %matrixValue : f64
- linalg.yield %product : f64
- } -> tensor<4x8xf64>
- return %result : tensor<4x8xf64>
-}
-
-// ITER-LABEL: func.func @sparse_broadcast(
-// ITER: sparse_tensor.iterate
-// ITER: scf.for
-
-// CHECK-LABEL: func.func @sparse_broadcast(
-// CHECK: %[[BROADCAST_C0:.*]] = arith.constant 0 : index
-// CHECK: %[[BROADCAST_C8:.*]] = arith.constant 8 : index
-// CHECK: scf.for %[[BROADCAST_ROW_POS:.*]] = %{{.*}} to %{{.*}} step %{{.*}} {
-// CHECK: %[[BROADCAST_ROW:.*]] = memref.load %{{.*}}[%[[BROADCAST_ROW_POS]]]
-// CHECK: %[[BROADCAST_VALUE:.*]] = memref.load %{{.*}}[%[[BROADCAST_ROW_POS]]]
-// CHECK: scf.for %[[BROADCAST_COL:.*]] = %[[BROADCAST_C0]] to %[[BROADCAST_C8]] step %{{.*}} {
-// CHECK: memref.store %[[BROADCAST_VALUE]], %{{.*}}[%[[BROADCAST_ROW]], %[[BROADCAST_COL]]]
-func.func @sparse_broadcast(%input: tensor<4xf64, #VEC>,
- %output: tensor<4x8xf64>) -> tensor<4x8xf64> {
- %result = linalg.generic {
- indexing_maps = [
- affine_map<(i, j) -> (i)>,
- affine_map<(i, j) -> (i, j)>
- ],
- iterator_types = ["parallel", "parallel"]
- } ins(%input : tensor<4xf64, #VEC>) outs(%output : tensor<4x8xf64>) {
- ^bb0(%inputValue: f64, %outputValue: f64):
- linalg.yield %inputValue : f64
- } -> tensor<4x8xf64>
- return %result : tensor<4x8xf64>
-}
More information about the Mlir-commits
mailing list