[mlir][sparse] Refactor the convert operator conversion to support codegen for the...
authorbixia1 <bixia@google.com>
Fri, 21 Oct 2022 15:25:45 +0000 (08:25 -0700)
committerbixia1 <bixia@google.com>
Fri, 21 Oct 2022 15:52:47 +0000 (08:52 -0700)
Outline the code that generates the loop structure to iterate over a dense
tensor or a sparse constant to genDenseTensorOrSparseConstantIterLoop.

Move a few routines to CodegenUtils for sharing.

Reviewed By: wrengr

Differential Revision: https://reviews.llvm.org/D136210

mlir/lib/Dialect/SparseTensor/Transforms/CodegenUtils.cpp
mlir/lib/Dialect/SparseTensor/Transforms/CodegenUtils.h
mlir/lib/Dialect/SparseTensor/Transforms/SparseTensorConversion.cpp
mlir/test/Dialect/SparseTensor/convert_dense2sparse.mlir

index e8c561b..742ebe3 100644 (file)
@@ -13,6 +13,7 @@
 #include "mlir/Dialect/Linalg/IR/Linalg.h"
 #include "mlir/Dialect/Linalg/Utils/Utils.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/Tensor/IR/Tensor.h"
 #include "mlir/IR/Matchers.h"
 #include "mlir/IR/Types.h"
 #include "mlir/IR/Value.h"
@@ -39,6 +40,56 @@ static Value genIndexLoad(OpBuilder &builder, Location loc, Value ptr,
   return load;
 }
 
+/// If the tensor is a sparse constant, generates and returns the pair of
+/// the constants for the indices and the values.
+static Optional<std::pair<Value, Value>>
+genSplitSparseConstant(OpBuilder &builder, Location loc, Value tensor) {
+  if (auto constOp = tensor.getDefiningOp<arith::ConstantOp>()) {
+    if (auto attr = constOp.getValue().dyn_cast<SparseElementsAttr>()) {
+      DenseElementsAttr indicesAttr = attr.getIndices();
+      Value indices = builder.create<arith::ConstantOp>(loc, indicesAttr);
+      DenseElementsAttr valuesAttr = attr.getValues();
+      Value values = builder.create<arith::ConstantOp>(loc, valuesAttr);
+      return std::make_pair(indices, values);
+    }
+  }
+  return {};
+}
+
+/// Generates the code to copy the index at indices[ivs] to ind, and return
+/// the value at value[ivs].
+static Value genIndexAndValueForSparse(OpBuilder &builder, Location loc,
+                                       Value indices, Value values,
+                                       SmallVectorImpl<Value> &indicesArray,
+                                       ValueRange ivs, unsigned rank) {
+  for (unsigned i = 0; i < rank; i++) {
+    Value idx = constantIndex(builder, loc, i);
+    Value val = builder.create<tensor::ExtractOp>(loc, indices,
+                                                  ValueRange{ivs[0], idx});
+    val = builder.create<arith::IndexCastOp>(loc, builder.getIndexType(), val);
+    // builder.create<memref::StoreOp>(loc, val, ind, idx);
+    indicesArray.push_back(val);
+  }
+  return builder.create<tensor::ExtractOp>(loc, values, ivs[0]);
+}
+
+/// Generates the code to read the value from tensor[ivs], and conditionally
+/// stores the indices ivs to the memory in ind. The generated code looks like
+/// the following and the insertion point after this routine is inside the
+/// if-then branch behind the assignment to ind. This is to ensure that the
+/// code that uses the ind, such as an addEltX call generated after, is inside
+/// the if-then branch.
+///    if (tensor[ivs] != 0)
+///      ind = ivs
+static Value genIndexAndValueForDense(OpBuilder &builder, Location loc,
+                                      Value tensor,
+                                      SmallVectorImpl<Value> &indicesArray,
+                                      ValueRange ivs) {
+  Value val = genValueForDense(builder, loc, tensor, ivs);
+  indicesArray.append(ivs.begin(), ivs.end());
+  return val;
+}
+
 //===----------------------------------------------------------------------===//
 // Sparse tensor loop emitter class implementations
 //===----------------------------------------------------------------------===//
@@ -573,3 +624,79 @@ Value mlir::sparse_tensor::genAllocaScalar(OpBuilder &builder, Location loc,
                                            Type tp) {
   return builder.create<memref::AllocaOp>(loc, MemRefType::get({}, tp));
 }
+
+Value mlir::sparse_tensor::allocDenseTensor(OpBuilder &builder, Location loc,
+                                            RankedTensorType tensorTp,
+                                            ValueRange sizes) {
+  Type elemTp = tensorTp.getElementType();
+  auto shape = tensorTp.getShape();
+  auto memTp = MemRefType::get(shape, elemTp);
+  SmallVector<Value> dynamicSizes;
+  for (unsigned i = 0, rank = tensorTp.getRank(); i < rank; i++) {
+    if (shape[i] == ShapedType::kDynamicSize)
+      dynamicSizes.push_back(sizes[i]);
+  }
+  Value mem = builder.create<memref::AllocOp>(loc, memTp, dynamicSizes);
+  Value zero = constantZero(builder, loc, elemTp);
+  builder.create<linalg::FillOp>(loc, ValueRange{zero}, ValueRange{mem});
+  return mem;
+}
+
+Value mlir::sparse_tensor::genValueForDense(OpBuilder &builder, Location loc,
+                                            Value tensor, ValueRange ivs) {
+  Value val = builder.create<tensor::ExtractOp>(loc, tensor, ivs);
+  Value cond = genIsNonzero(builder, loc, val);
+  scf::IfOp ifOp = builder.create<scf::IfOp>(loc, cond, /*else*/ false);
+  builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
+  return val;
+}
+
+void mlir::sparse_tensor::genDenseTensorOrSparseConstantIterLoop(
+    OpBuilder &builder, Location loc, Value src, unsigned rank,
+    function_ref<void(OpBuilder &, Location, Value, ValueRange)> bodyBuilder) {
+  SmallVector<Value, 4> indicesArray;
+  SmallVector<Value> lo;
+  SmallVector<Value> hi;
+  SmallVector<Value> st;
+  Value zero = constantIndex(builder, loc, 0);
+  Value one = constantIndex(builder, loc, 1);
+  auto indicesValues = genSplitSparseConstant(builder, loc, src);
+  bool isCOOConstant = indicesValues.has_value();
+  Value indices;
+  Value values;
+  if (isCOOConstant) {
+    indices = indicesValues->first;
+    values = indicesValues->second;
+    lo.push_back(zero);
+    hi.push_back(linalg::createOrFoldDimOp(builder, loc, values, 0));
+    st.push_back(one);
+  } else {
+    for (unsigned i = 0; i < rank; i++) {
+      lo.push_back(zero);
+      hi.push_back(linalg::createOrFoldDimOp(builder, loc, src, i));
+      st.push_back(one);
+    }
+  }
+
+  scf::buildLoopNest(
+      builder, loc, lo, hi, st, {},
+      [&](OpBuilder &builder, Location loc, ValueRange ivs,
+          ValueRange args) -> scf::ValueVector {
+        Value val;
+        if (isCOOConstant)
+          val = genIndexAndValueForSparse(builder, loc, indices, values,
+                                          indicesArray, ivs, rank);
+        else
+          val = genIndexAndValueForDense(builder, loc, src, indicesArray, ivs);
+        bodyBuilder(builder, loc, val, indicesArray);
+        return {};
+      });
+}
+
+void mlir::sparse_tensor::sizesFromSrc(OpBuilder &builder,
+                                       SmallVector<Value, 4> &sizes,
+                                       Location loc, Value src) {
+  unsigned rank = src.getType().cast<ShapedType>().getRank();
+  for (unsigned i = 0; i < rank; i++)
+    sizes.push_back(linalg::createOrFoldDimOp(builder, loc, src, i));
+}
index 9476b4c..d3fa3e6 100644 (file)
@@ -259,6 +259,45 @@ Value genAlloca(OpBuilder &builder, Location loc, unsigned sz, Type tp);
 /// of the given type, and returns the `memref<$tp>`.
 Value genAllocaScalar(OpBuilder &builder, Location loc, Type tp);
 
+/// Generates code to allocate a buffer of the given type, and zero
+/// initialize it.  If the buffer type has any dynamic sizes, then the
+/// `sizes` parameter should be as filled by sizesFromPtr(); that way
+/// we can reuse the genDimSizeCall() results generated by sizesFromPtr().
+Value allocDenseTensor(OpBuilder &builder, Location loc,
+                       RankedTensorType tensorTp, ValueRange sizes);
+
+/// Generates the code to read the value from tensor[ivs]. The generated code
+/// looks like the following and the insertion point after this routine is
+/// inside the if-then branch behind the assignment to ind.
+///    if (tensor[ivs] != 0)
+///      insert_point
+Value genValueForDense(OpBuilder &builder, Location loc, Value tensor,
+                       ValueRange ivs);
+
+/// Generates the loop structure to iterate over a dense tensor or a sparse
+/// tensor constant to support the lowering of dense-to-sparse convert operator.
+//
+// The loop to iterate a dense tensor:
+//   for i1 in dim1
+//    ..
+//     for ik in dimk
+//       val = a[i1,..,ik]
+//       if val != 0
+//         loop-body
+//
+// The loop to iterate a sparse tensor constant:
+//   for i in range(NNZ)
+//     val = values[i]
+//     [i1,..,ik] = indices[i]
+//     loop-body
+void genDenseTensorOrSparseConstantIterLoop(
+    OpBuilder &builder, Location loc, Value src, unsigned rank,
+    function_ref<void(OpBuilder &, Location, Value, ValueRange)> bodyBuilder);
+
+/// Populates given sizes array from dense tensor or sparse tensor constant.
+void sizesFromSrc(OpBuilder &builder, SmallVector<Value, 4> &sizes,
+                  Location loc, Value src);
+
 //===----------------------------------------------------------------------===//
 // Inlined constant generators.
 //
index 4b9c2dd..4011207 100644 (file)
@@ -110,14 +110,6 @@ static void sizesFromType(OpBuilder &builder, SmallVector<Value, 4> &sizes,
   }
 }
 
-/// Populates given sizes array from source.
-static void sizesFromSrc(OpBuilder &builder, SmallVector<Value, 4> &sizes,
-                         Location loc, Value src) {
-  unsigned rank = src.getType().cast<ShapedType>().getRank();
-  for (unsigned i = 0; i < rank; i++)
-    sizes.push_back(linalg::createOrFoldDimOp(builder, loc, src, i));
-}
-
 /// Populates the given sizes array for concatenation from type (for static
 /// sizes) and from an already-converted opaque pointer source (for dynamic
 /// sizes).
@@ -213,38 +205,6 @@ static void newParams(OpBuilder &builder, SmallVector<Value, 8> &params,
   params.push_back(ptr);
 }
 
-/// Generates the code to read the value from tensor[ivs].The generated code
-/// looks like the following and the insertion point after this routine is
-/// inside the if-then branch behind the assignment to ind.
-///    if (tensor[ivs] != 0)
-///      insert_point
-static Value genValueForDense(OpBuilder &builder, Location loc, Value tensor,
-                              ValueRange ivs) {
-  Value val = builder.create<tensor::ExtractOp>(loc, tensor, ivs);
-  Value cond = genIsNonzero(builder, loc, val);
-  scf::IfOp ifOp = builder.create<scf::IfOp>(loc, cond, /*else*/ false);
-  builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
-  return val;
-}
-
-/// Generates the code to read the value from tensor[ivs], and conditionally
-/// stores the indices ivs to the memory in ind. The generated code looks like
-/// the following and the insertion point after this routine is inside the
-/// if-then branch behind the assignment to ind. This is to ensure that the
-/// addEltX call generated after is inside the if-then branch.
-///    if (tensor[ivs] != 0)
-///      ind = ivs
-static Value genIndexAndValueForDense(OpBuilder &builder, Location loc,
-                                      Value tensor, Value ind, ValueRange ivs) {
-  Value val = genValueForDense(builder, loc, tensor, ivs);
-  unsigned i = 0;
-  for (auto iv : ivs) {
-    Value idx = constantIndex(builder, loc, i++);
-    builder.create<memref::StoreOp>(loc, iv, ind, idx);
-  }
-  return val;
-}
-
 /// Generates a call to release/delete a `SparseTensorCOO`.
 static void genDelCOOCall(OpBuilder &builder, Location loc, Type elemTp,
                           Value coo) {
@@ -287,57 +247,6 @@ static Value genGetNextCall(OpBuilder &builder, Location loc, Value iter,
       .getResult(0);
 }
 
-/// If the tensor is a sparse constant, generates and returns the pair of
-/// the constants for the indices and the values.
-static Optional<std::pair<Value, Value>>
-genSplitSparseConstant(OpBuilder &builder, Location loc, Value tensor) {
-  if (auto constOp = tensor.getDefiningOp<arith::ConstantOp>()) {
-    if (auto attr = constOp.getValue().dyn_cast<SparseElementsAttr>()) {
-      DenseElementsAttr indicesAttr = attr.getIndices();
-      Value indices = builder.create<arith::ConstantOp>(loc, indicesAttr);
-      DenseElementsAttr valuesAttr = attr.getValues();
-      Value values = builder.create<arith::ConstantOp>(loc, valuesAttr);
-      return std::make_pair(indices, values);
-    }
-  }
-  return {};
-}
-
-/// Generates the code to copy the index at indices[ivs] to ind, and return
-/// the value at value[ivs].
-static Value genIndexAndValueForSparse(OpBuilder &builder, Location loc,
-                                       Value indices, Value values, Value ind,
-                                       ValueRange ivs, unsigned rank) {
-  for (unsigned i = 0; i < rank; i++) {
-    Value idx = constantIndex(builder, loc, i);
-    Value val = builder.create<tensor::ExtractOp>(loc, indices,
-                                                  ValueRange{ivs[0], idx});
-    val = builder.create<arith::IndexCastOp>(loc, builder.getIndexType(), val);
-    builder.create<memref::StoreOp>(loc, val, ind, idx);
-  }
-  return builder.create<tensor::ExtractOp>(loc, values, ivs[0]);
-}
-
-/// Generates code to allocate a buffer of the given type, and zero
-/// initialize it.  If the buffer type has any dynamic sizes, then the
-/// `sizes` parameter should be as filled by sizesFromPtr(); that way
-/// we can reuse the genDimSizeCall() results generated by sizesFromPtr().
-static Value allocDenseTensor(OpBuilder &builder, Location loc,
-                              RankedTensorType tensorTp, ValueRange sizes) {
-  Type elemTp = tensorTp.getElementType();
-  auto shape = tensorTp.getShape();
-  auto memTp = MemRefType::get(shape, elemTp);
-  SmallVector<Value> dynamicSizes;
-  for (unsigned i = 0, rank = tensorTp.getRank(); i < rank; i++) {
-    if (shape[i] == ShapedType::kDynamicSize)
-      dynamicSizes.push_back(sizes[i]);
-  }
-  Value mem = builder.create<memref::AllocOp>(loc, memTp, dynamicSizes);
-  Value zero = constantZero(builder, loc, elemTp);
-  builder.create<linalg::FillOp>(loc, ValueRange{zero}, ValueRange{mem});
-  return mem;
-}
-
 /// Generates code to deallocate a dense buffer.
 static void deallocDenseTensor(OpBuilder &builder, Location loc, Value buffer) {
   builder.create<memref::DeallocOp>(loc, buffer);
@@ -905,43 +814,17 @@ public:
     Value coo = genNewCall(rewriter, loc, params);
     Value ind = genAlloca(rewriter, loc, rank, rewriter.getIndexType());
     Value perm = params[2];
-    SmallVector<Value> lo;
-    SmallVector<Value> hi;
-    SmallVector<Value> st;
-    Value zero = constantIndex(rewriter, loc, 0);
-    Value one = constantIndex(rewriter, loc, 1);
-    auto indicesValues = genSplitSparseConstant(rewriter, loc, src);
-    bool isCOOConstant = indicesValues.has_value();
-    Value indices;
-    Value values;
-    if (isCOOConstant) {
-      indices = indicesValues->first;
-      values = indicesValues->second;
-      lo.push_back(zero);
-      hi.push_back(linalg::createOrFoldDimOp(rewriter, loc, values, 0));
-      st.push_back(one);
-    } else {
-      for (unsigned i = 0; i < rank; i++) {
-        lo.push_back(zero);
-        hi.push_back(linalg::createOrFoldDimOp(rewriter, loc, src, i));
-        st.push_back(one);
-      }
-    }
     Type eltType = stp.getElementType();
     Value elemPtr = genAllocaScalar(rewriter, loc, eltType);
-    scf::buildLoopNest(
-        rewriter, op.getLoc(), lo, hi, st, {},
-        [&](OpBuilder &builder, Location loc, ValueRange ivs,
-            ValueRange args) -> scf::ValueVector {
-          Value val;
-          if (isCOOConstant)
-            val = genIndexAndValueForSparse(rewriter, loc, indices, values, ind,
-                                            ivs, rank);
-          else
-            val = genIndexAndValueForDense(rewriter, loc, src, ind, ivs);
+    genDenseTensorOrSparseConstantIterLoop(
+        rewriter, loc, src, rank,
+        [&](OpBuilder &builder, Location loc, Value val, ValueRange indices) {
+          for (unsigned i = 0; i < rank; i++) {
+            Value idx = constantIndex(builder, loc, i);
+            builder.create<memref::StoreOp>(loc, indices[i], ind, idx);
+          }
           builder.create<memref::StoreOp>(loc, val, elemPtr);
-          genAddEltCall(rewriter, loc, eltType, coo, elemPtr, ind, perm);
-          return {};
+          genAddEltCall(builder, loc, eltType, coo, elemPtr, ind, perm);
         });
     // Final call to construct sparse tensor storage.
     params[6] = constantAction(rewriter, loc, Action::kFromCOO);
index e8a1dc5..13772e8 100644 (file)
@@ -123,9 +123,9 @@ func.func @sparse_convert_2d(%arg0: tensor<2x4xf64>) -> tensor<2x4xf64, #CSR> {
 //       CHECK: %[[N:.*]] = memref.cast %[[M]] : memref<2xindex> to memref<?xindex>
 //       CHECK: %[[BUF:.*]] = memref.alloca() : memref<f32>
 //       CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[C2]] step %[[C1]] {
-//       CHECK:   memref.store %{{.*}}, %[[M]][%[[C0]]] : memref<2xindex>
-//       CHECK:   memref.store %{{.*}}, %[[M]][%[[C1]]] : memref<2xindex>
-//       CHECK:   %[[V:.*]] = tensor.extract %{{.*}}[%[[I]]] : tensor<2xf32>
+//       CHECK-DAG:   memref.store %{{.*}}, %[[M]][%[[C0]]] : memref<2xindex>
+//       CHECK-DAG:   memref.store %{{.*}}, %[[M]][%[[C1]]] : memref<2xindex>
+//       CHECK-DAG:   %[[V:.*]] = tensor.extract %{{.*}}[%[[I]]] : tensor<2xf32>
 //       CHECK:   memref.store %[[V]], %[[BUF]][] : memref<f32>
 //       CHECK:   call @addEltF32(%{{.*}}, %[[BUF]], %[[N]], %{{.*}})
 //       CHECK: }