[Mlir-commits] [mlir] ee8259d - [mlir][sparse] Fix use-after-free crash in SparseSpaceCollapsePass (#184001)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue Mar 3 03:43:08 PST 2026


Author: Mehdi Amini
Date: 2026-03-03T12:43:04+01:00
New Revision: ee8259dcca82849735225c8ea1b215ed7fef7648

URL: https://github.com/llvm/llvm-project/commit/ee8259dcca82849735225c8ea1b215ed7fef7648
DIFF: https://github.com/llvm/llvm-project/commit/ee8259dcca82849735225c8ea1b215ed7fef7648.diff

LOG: [mlir][sparse] Fix use-after-free crash in SparseSpaceCollapsePass (#184001)

The `SparseSpaceCollapsePass::runOnOperation()` was calling
`collapseSparseSpace()` inside the `func->walk(...)` callback. The
collapse erases the outer `IterateOp` (and all its nested ops including
inner `ExtractIterSpaceOp`s), while the walk iterator still holds a
reference to one of those nested ops as its current position. The walk
then tries to advance to the next op, which has already been freed,
causing a segfault.

Fix this by collecting all collapsable groups during the walk without
mutating the IR, then processing them after the walk completes.

Fixes #130021

Added: 
    

Modified: 
    mlir/lib/Dialect/SparseTensor/Transforms/SparseSpaceCollapse.cpp
    mlir/test/Dialect/SparseTensor/sparse_space_collapse.mlir

Removed: 
    


################################################################################
diff  --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseSpaceCollapse.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseSpaceCollapse.cpp
index 81cd3296de294..337eb279b7154 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseSpaceCollapse.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseSpaceCollapse.cpp
@@ -178,17 +178,25 @@ struct SparseSpaceCollapsePass
     // %space2 = extract_space %t2 ...
     // sparse_tensor.iterate(%sp1) ...
     //
+    // Collect all groups to collapse before performing any IR mutations.
+    // Mutating (erasing) ops during the walk would invalidate the walk's
+    // internal iterator and cause use-after-free crashes.
+    SmallVector<SmallVector<CollapseSpaceInfo>> groups;
     SmallVector<CollapseSpaceInfo> toCollapse;
     func->walk([&](ExtractIterSpaceOp op) {
       if (!legalToCollapse(toCollapse, op)) {
-        // if not legal to collapse one more space, collapse the existing ones
-        // and clear.
-        collapseSparseSpace(toCollapse);
+        // Save the current group and start a new one.
+        groups.push_back(std::move(toCollapse));
         toCollapse.clear();
+        // Try to start a new group with the current op.
+        legalToCollapse(toCollapse, op);
       }
     });
+    groups.push_back(std::move(toCollapse));
 
-    collapseSparseSpace(toCollapse);
+    // Apply all collapse transformations after the walk is complete.
+    for (auto &group : groups)
+      collapseSparseSpace(group);
   }
 };
 

diff  --git a/mlir/test/Dialect/SparseTensor/sparse_space_collapse.mlir b/mlir/test/Dialect/SparseTensor/sparse_space_collapse.mlir
index b5d041273f440..73bd9dacf8c24 100644
--- a/mlir/test/Dialect/SparseTensor/sparse_space_collapse.mlir
+++ b/mlir/test/Dialect/SparseTensor/sparse_space_collapse.mlir
@@ -34,3 +34,40 @@ func.func @sparse_sparse_collapse(%sp : tensor<4x8xf32, #COO>) -> index {
   }
   return %r1 : index
 }
+
+// Verify that --sparse-space-collapse does not crash when an
+// ExtractIterSpaceOp inside a collapsable loop body is not consumed by an
+// IterateOp. Previously the pass erased ops during the walk, invalidating the
+// walk iterator and causing a use-after-free. See:
+// https://github.com/llvm/llvm-project/issues/130021
+
+// The inner %l3 (from %sp2) is not consumed by an IterateOp, so it cannot be
+// collapsed. Before the fix, processing the collapsable group {%l1,%l2} during
+// the walk would erase %r1 (and everything nested inside, including %l3),
+// causing the walk to access freed memory on the next step.
+
+// CHECK-LABEL: func.func @no_crash_unconsumed_iter_space(
+// CHECK:         sparse_tensor.extract_iteration_space {{.*}} lvls = 0 to 2
+// CHECK:         sparse_tensor.iterate
+// CHECK:           sparse_tensor.extract_iteration_space {{.*}} lvls = 0
+func.func @no_crash_unconsumed_iter_space(
+    %sp : tensor<4x8xf32, #COO>, %sp2 : tensor<4x8xf32, #COO>) -> index {
+  %i = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %l1 = sparse_tensor.extract_iteration_space %sp lvls = 0
+      : tensor<4x8xf32, #COO> -> !sparse_tensor.iter_space<#COO, lvls = 0>
+  %r1 = sparse_tensor.iterate %it1 in %l1 iter_args(%outer = %i): !sparse_tensor.iter_space<#COO, lvls = 0 to 1> -> index {
+    %l2 = sparse_tensor.extract_iteration_space %sp at %it1 lvls = 1
+        : tensor<4x8xf32, #COO>, !sparse_tensor.iterator<#COO, lvls = 0 to 1> -> !sparse_tensor.iter_space<#COO, lvls = 1>
+    %r2 = sparse_tensor.iterate %it2 in %l2 iter_args(%inner = %outer): !sparse_tensor.iter_space<#COO, lvls = 1 to 2> -> index {
+      // This space is from a 
diff erent tensor and is not consumed by an IterateOp,
+      // so it breaks the collapsable chain.  It must not cause a crash.
+      %l3 = sparse_tensor.extract_iteration_space %sp2 lvls = 0
+          : tensor<4x8xf32, #COO> -> !sparse_tensor.iter_space<#COO, lvls = 0>
+      %k = arith.addi %inner, %c1 : index
+      sparse_tensor.yield %k : index
+    }
+    sparse_tensor.yield %r2 : index
+  }
+  return %r1 : index
+}


        


More information about the Mlir-commits mailing list