brintos

brintos / llvm-project-archived public Read only

0
0
Text · 102.2 KiB · 3962e3e Raw
2801 lines · cpp
1//===- SparseTensorDialect.cpp - Sparse tensor dialect implementation -----===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include <utility>10 11#include "Detail/DimLvlMapParser.h"12 13#include "mlir/Dialect/SparseTensor/IR/Enums.h"14#include "mlir/Dialect/SparseTensor/IR/SparseTensor.h"15#include "mlir/Dialect/SparseTensor/IR/SparseTensorStorageLayout.h"16#include "mlir/Dialect/SparseTensor/IR/SparseTensorType.h"17 18#include "mlir/Dialect/Arith/IR/Arith.h"19#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"20#include "mlir/Dialect/Complex/IR/Complex.h"21#include "mlir/Dialect/Utils/StaticValueUtils.h"22#include "mlir/IR/Builders.h"23#include "mlir/IR/DialectImplementation.h"24#include "mlir/IR/OpImplementation.h"25#include "mlir/IR/PatternMatch.h"26#include "llvm/ADT/TypeSwitch.h"27#include "llvm/Support/FormatVariadic.h"28 29#define GET_ATTRDEF_CLASSES30#include "mlir/Dialect/SparseTensor/IR/SparseTensorAttrDefs.cpp.inc"31#include "mlir/Dialect/SparseTensor/IR/SparseTensorAttrEnums.cpp.inc"32 33// Forward declarations, following custom print/parsing methods are referenced34// by the generated code for SparseTensorTypes.td.35static mlir::ParseResult parseLevelRange(mlir::AsmParser &,36                                         mlir::sparse_tensor::Level &,37                                         mlir::sparse_tensor::Level &);38static void printLevelRange(mlir::AsmPrinter &, mlir::sparse_tensor::Level,39                            mlir::sparse_tensor::Level);40 41#define GET_TYPEDEF_CLASSES42#include "mlir/Dialect/SparseTensor/IR/SparseTensorTypes.cpp.inc"43 44using namespace mlir;45using namespace mlir::sparse_tensor;46 47// Support hashing LevelType such that SparseTensorEncodingAttr can be hashed as48// well.49namespace mlir::sparse_tensor {50llvm::hash_code hash_value(LevelType lt) {51  return llvm::hash_value(static_cast<uint64_t>(lt));52}53} // namespace mlir::sparse_tensor54 55//===----------------------------------------------------------------------===//56// Local Convenience Methods.57//===----------------------------------------------------------------------===//58 59static constexpr bool acceptBitWidth(unsigned bitWidth) {60  switch (bitWidth) {61  case 0:62  case 8:63  case 16:64  case 32:65  case 64:66    return true;67  default:68    return false;69  }70}71 72static SmallVector<Size>73getSparseFieldShape(const SparseTensorEncodingAttr enc,74                    std::optional<ArrayRef<int64_t>> dimShape) {75  assert(enc);76  // With only encoding, we can not determine the static shape for leading77  // batch levels, we therefore return a dynamic shape memref instead.78  SmallVector<int64_t> memrefShape(enc.getBatchLvlRank(), ShapedType::kDynamic);79  if (dimShape.has_value()) {80    // If the actual tensor shape is provided, we can then refine the leading81    // batch dimension.82    SmallVector<int64_t> lvlShape =83        enc.translateShape(*dimShape, CrdTransDirectionKind::dim2lvl);84    memrefShape.assign(lvlShape.begin(),85                       lvlShape.begin() + enc.getBatchLvlRank());86  }87  // Another dynamic dimension to store the sparse level.88  memrefShape.push_back(ShapedType::kDynamic);89  return memrefShape;90}91 92//===----------------------------------------------------------------------===//93// SparseTensorDialect StorageLayout.94//===----------------------------------------------------------------------===//95 96static constexpr Level kInvalidLevel = -1u;97static constexpr Level kInvalidFieldIndex = -1u;98static constexpr FieldIndex kDataFieldStartingIdx = 0;99 100void StorageLayout::foreachField(101    llvm::function_ref<bool(FieldIndex, SparseTensorFieldKind, Level,102                            LevelType)>103        callback) const {104  const auto lvlTypes = enc.getLvlTypes();105  const Level lvlRank = enc.getLvlRank();106  SmallVector<COOSegment> cooSegs = enc.getCOOSegments();107  FieldIndex fieldIdx = kDataFieldStartingIdx;108 109  ArrayRef cooSegsRef = cooSegs;110  // Per-level storage.111  for (Level l = 0; l < lvlRank; /*l += 1 or l += AoSCooLen*/) {112    const auto lt = lvlTypes[l];113    if (isWithPosLT(lt)) {114      if (!(callback(fieldIdx++, SparseTensorFieldKind::PosMemRef, l, lt)))115        return;116    }117    if (isWithCrdLT(lt)) {118      if (!(callback(fieldIdx++, SparseTensorFieldKind::CrdMemRef, l, lt)))119        return;120    }121    if (!cooSegsRef.empty() && cooSegsRef.front().isSegmentStart(l)) {122      if (!cooSegsRef.front().isSoA) {123        // AoS COO, all singletons are fused into one memrefs. Skips the entire124        // COO segement.125        l = cooSegsRef.front().lvlRange.second;126      } else {127        // SoA COO, each singleton level has one memref.128        l++;129      }130      // Expire handled COO segment.131      cooSegsRef = cooSegsRef.drop_front();132    } else {133      // Non COO levels.134      l++;135    }136  }137  // The values array.138  if (!(callback(fieldIdx++, SparseTensorFieldKind::ValMemRef, kInvalidLevel,139                 LevelFormat::Undef)))140    return;141  // Put metadata at the end.142  if (!(callback(fieldIdx++, SparseTensorFieldKind::StorageSpec, kInvalidLevel,143                 LevelFormat::Undef)))144    return;145}146 147void sparse_tensor::foreachFieldAndTypeInSparseTensor(148    SparseTensorType stt,149    llvm::function_ref<bool(Type, FieldIndex, SparseTensorFieldKind, Level,150                            LevelType)>151        callback) {152  assert(stt.hasEncoding());153 154  SmallVector<int64_t> memrefShape =155      getSparseFieldShape(stt.getEncoding(), stt.getDimShape());156 157  const Type specType = StorageSpecifierType::get(stt.getEncoding());158  // memref<[batch] x ? x pos>  positions159  const Type posMemType = MemRefType::get(memrefShape, stt.getPosType());160  // memref<[batch] x ? x crd>  coordinates161  const Type crdMemType = MemRefType::get(memrefShape, stt.getCrdType());162  // memref<[batch] x ? x eltType> values163  const Type valMemType = MemRefType::get(memrefShape, stt.getElementType());164 165  StorageLayout(stt).foreachField([specType, posMemType, crdMemType, valMemType,166                                   callback](FieldIndex fieldIdx,167                                             SparseTensorFieldKind fieldKind,168                                             Level lvl, LevelType lt) -> bool {169    switch (fieldKind) {170    case SparseTensorFieldKind::StorageSpec:171      return callback(specType, fieldIdx, fieldKind, lvl, lt);172    case SparseTensorFieldKind::PosMemRef:173      return callback(posMemType, fieldIdx, fieldKind, lvl, lt);174    case SparseTensorFieldKind::CrdMemRef:175      return callback(crdMemType, fieldIdx, fieldKind, lvl, lt);176    case SparseTensorFieldKind::ValMemRef:177      return callback(valMemType, fieldIdx, fieldKind, lvl, lt);178    };179    llvm_unreachable("unrecognized field kind");180  });181}182 183unsigned StorageLayout::getNumFields() const {184  unsigned numFields = 0;185  foreachField([&numFields](FieldIndex, SparseTensorFieldKind, Level,186                            LevelType) -> bool {187    numFields++;188    return true;189  });190  return numFields;191}192 193unsigned StorageLayout::getNumDataFields() const {194  unsigned numFields = 0; // one value memref195  foreachField([&numFields](FieldIndex fidx, SparseTensorFieldKind, Level,196                            LevelType) -> bool {197    if (fidx >= kDataFieldStartingIdx)198      numFields++;199    return true;200  });201  numFields -= 1; // the last field is StorageSpecifier202  assert(numFields == getNumFields() - kDataFieldStartingIdx - 1);203  return numFields;204}205 206std::pair<FieldIndex, unsigned>207StorageLayout::getFieldIndexAndStride(SparseTensorFieldKind kind,208                                      std::optional<Level> lvl) const {209  FieldIndex fieldIdx = kInvalidFieldIndex;210  unsigned stride = 1;211  if (kind == SparseTensorFieldKind::CrdMemRef) {212    assert(lvl.has_value());213    const Level cooStart = enc.getAoSCOOStart();214    const Level lvlRank = enc.getLvlRank();215    if (lvl.value() >= cooStart && lvl.value() < lvlRank) {216      lvl = cooStart;217      stride = lvlRank - cooStart;218    }219  }220  foreachField([lvl, kind, &fieldIdx](FieldIndex fIdx,221                                      SparseTensorFieldKind fKind, Level fLvl,222                                      LevelType lt) -> bool {223    if ((lvl && fLvl == lvl.value() && kind == fKind) ||224        (kind == fKind && fKind == SparseTensorFieldKind::ValMemRef)) {225      fieldIdx = fIdx;226      // Returns false to break the iteration.227      return false;228    }229    return true;230  });231  assert(fieldIdx != kInvalidFieldIndex);232  return std::pair<FieldIndex, unsigned>(fieldIdx, stride);233}234 235//===----------------------------------------------------------------------===//236// SparseTensorDialect Attribute Methods.237//===----------------------------------------------------------------------===//238 239std::optional<uint64_t> SparseTensorDimSliceAttr::getStatic(int64_t v) {240  return isDynamic(v) ? std::nullopt241                      : std::make_optional(static_cast<uint64_t>(v));242}243 244std::optional<uint64_t> SparseTensorDimSliceAttr::getStaticOffset() const {245  return getStatic(getOffset());246}247 248std::optional<uint64_t> SparseTensorDimSliceAttr::getStaticStride() const {249  return getStatic(getStride());250}251 252std::optional<uint64_t> SparseTensorDimSliceAttr::getStaticSize() const {253  return getStatic(getSize());254}255 256bool SparseTensorDimSliceAttr::isCompletelyDynamic() const {257  return isDynamic(getOffset()) && isDynamic(getStride()) &&258         isDynamic(getSize());259}260 261std::string SparseTensorDimSliceAttr::getStaticString(int64_t v) {262  return isDynamic(v) ? "?" : std::to_string(v);263}264 265void SparseTensorDimSliceAttr::print(llvm::raw_ostream &os) const {266  assert(getImpl() && "Uninitialized SparseTensorDimSliceAttr");267  os << '(';268  os << getStaticString(getOffset());269  os << ", ";270  os << getStaticString(getSize());271  os << ", ";272  os << getStaticString(getStride());273  os << ')';274}275 276void SparseTensorDimSliceAttr::print(AsmPrinter &printer) const {277  print(printer.getStream());278}279 280static ParseResult parseOptionalStaticSlice(int64_t &result,281                                            AsmParser &parser) {282  auto parseResult = parser.parseOptionalInteger(result);283  if (parseResult.has_value()) {284    if (parseResult.value().succeeded() && result < 0) {285      parser.emitError(286          parser.getCurrentLocation(),287          "expect positive value or ? for slice offset/size/stride");288      return failure();289    }290    return parseResult.value();291  }292 293  // Else, and '?' which represented dynamic slice294  result = SparseTensorDimSliceAttr::kDynamic;295  return parser.parseQuestion();296}297 298Attribute SparseTensorDimSliceAttr::parse(AsmParser &parser, Type type) {299  int64_t offset = kDynamic, size = kDynamic, stride = kDynamic;300 301  if (failed(parser.parseLParen()) ||302      failed(parseOptionalStaticSlice(offset, parser)) ||303      failed(parser.parseComma()) ||304      failed(parseOptionalStaticSlice(size, parser)) ||305      failed(parser.parseComma()) ||306      failed(parseOptionalStaticSlice(stride, parser)) ||307      failed(parser.parseRParen()))308    return {};309 310  return parser.getChecked<SparseTensorDimSliceAttr>(parser.getContext(),311                                                     offset, size, stride);312}313 314LogicalResult315SparseTensorDimSliceAttr::verify(function_ref<InFlightDiagnostic()> emitError,316                                 int64_t offset, int64_t size, int64_t stride) {317  if (!isDynamic(offset) && offset < 0)318    return emitError() << "expect non-negative value or ? for slice offset";319  if (!isDynamic(size) && size <= 0)320    return emitError() << "expect positive value or ? for slice size";321  if (!isDynamic(stride) && stride <= 0)322    return emitError() << "expect positive value or ? for slice stride";323  return success();324}325 326SparseTensorEncodingAttr327SparseTensorEncodingAttr::withDimToLvl(AffineMap dimToLvl) const {328  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");329  return SparseTensorEncodingAttr::get(330      getContext(), getLvlTypes(), dimToLvl, AffineMap(), getPosWidth(),331      getCrdWidth(), getExplicitVal(), getImplicitVal());332}333 334SparseTensorEncodingAttr335SparseTensorEncodingAttr::withDimToLvl(SparseTensorEncodingAttr enc) const {336  return withDimToLvl(enc ? enc.getDimToLvl() : AffineMap());337}338 339SparseTensorEncodingAttr SparseTensorEncodingAttr::withoutDimToLvl() const {340  return withDimToLvl(AffineMap());341}342 343SparseTensorEncodingAttr344SparseTensorEncodingAttr::withBitWidths(unsigned posWidth,345                                        unsigned crdWidth) const {346  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");347  return SparseTensorEncodingAttr::get(348      getContext(), getLvlTypes(), getDimToLvl(), getLvlToDim(), posWidth,349      crdWidth, getExplicitVal(), getImplicitVal());350}351 352SparseTensorEncodingAttr SparseTensorEncodingAttr::withoutBitWidths() const {353  return withBitWidths(0, 0);354}355 356SparseTensorEncodingAttr357SparseTensorEncodingAttr::withExplicitVal(Attribute explicitVal) const {358  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");359  return SparseTensorEncodingAttr::get(360      getContext(), getLvlTypes(), getDimToLvl(), getLvlToDim(), getPosWidth(),361      getCrdWidth(), explicitVal, getImplicitVal());362}363 364SparseTensorEncodingAttr SparseTensorEncodingAttr::withoutExplicitVal() const {365  return withExplicitVal(Attribute());366}367 368SparseTensorEncodingAttr369SparseTensorEncodingAttr::withImplicitVal(Attribute implicitVal) const {370  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");371  return SparseTensorEncodingAttr::get(372      getContext(), getLvlTypes(), getDimToLvl(), getLvlToDim(), getPosWidth(),373      getCrdWidth(), getExplicitVal(), implicitVal);374}375 376SparseTensorEncodingAttr SparseTensorEncodingAttr::withoutImplicitVal() const {377  return withImplicitVal(Attribute());378}379 380SparseTensorEncodingAttr SparseTensorEncodingAttr::withDimSlices(381    ArrayRef<SparseTensorDimSliceAttr> dimSlices) const {382  return SparseTensorEncodingAttr::get(383      getContext(), getLvlTypes(), getDimToLvl(), getLvlToDim(), getPosWidth(),384      getCrdWidth(), getExplicitVal(), getImplicitVal(), dimSlices);385}386 387SparseTensorEncodingAttr SparseTensorEncodingAttr::withoutDimSlices() const {388  return withDimSlices(ArrayRef<SparseTensorDimSliceAttr>{});389}390 391uint64_t SparseTensorEncodingAttr::getBatchLvlRank() const {392  ArrayRef<LevelType> lvlTypes = getLvlTypes();393  auto lastBatch = std::find_if(lvlTypes.rbegin(), lvlTypes.rend(), isBatchLT);394  return std::distance(lastBatch, lvlTypes.rend());395}396 397bool SparseTensorEncodingAttr::isAllDense() const {398  return !getImpl() || llvm::all_of(getLvlTypes(), isDenseLT);399}400 401bool SparseTensorEncodingAttr::isAllOrdered() const {402  return !getImpl() || llvm::all_of(getLvlTypes(), isOrderedLT);403}404 405Type SparseTensorEncodingAttr::getCrdElemType() const {406  if (!getImpl())407    return nullptr;408  if (getCrdWidth())409    return IntegerType::get(getContext(), getCrdWidth());410  return IndexType::get(getContext());411}412 413Type SparseTensorEncodingAttr::getPosElemType() const {414  if (!getImpl())415    return nullptr;416  if (getPosWidth())417    return IntegerType::get(getContext(), getPosWidth());418  return IndexType::get(getContext());419}420 421MemRefType SparseTensorEncodingAttr::getCrdMemRefType(422    std::optional<ArrayRef<int64_t>> dimShape) const {423  SmallVector<Size> shape = getSparseFieldShape(*this, dimShape);424  return MemRefType::get(shape, getCrdElemType());425}426 427MemRefType SparseTensorEncodingAttr::getPosMemRefType(428    std::optional<ArrayRef<int64_t>> dimShape) const {429  SmallVector<Size> shape = getSparseFieldShape(*this, dimShape);430  return MemRefType::get(shape, getPosElemType());431}432 433bool SparseTensorEncodingAttr::isIdentity() const {434  return !getImpl() || !getDimToLvl() || getDimToLvl().isIdentity();435}436 437bool SparseTensorEncodingAttr::isPermutation() const {438  return !getImpl() || !getDimToLvl() || getDimToLvl().isPermutation();439}440 441Dimension SparseTensorEncodingAttr::getDimRank() const {442  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");443  const auto dimToLvl = getDimToLvl();444  return dimToLvl ? dimToLvl.getNumDims() : getLvlRank();445}446 447Level SparseTensorEncodingAttr::getLvlRank() const {448  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");449  return getLvlTypes().size();450}451 452LevelType SparseTensorEncodingAttr::getLvlType(Level l) const {453  if (!getImpl())454    return LevelFormat::Batch;455  assert(l < getLvlRank() && "Level is out of bounds");456  return getLvlTypes()[l];457}458 459bool SparseTensorEncodingAttr::isSlice() const {460  assert(getImpl() && "Uninitialized SparseTensorEncodingAttr");461  return !getDimSlices().empty();462}463 464SparseTensorDimSliceAttr465SparseTensorEncodingAttr::getDimSlice(Dimension dim) const {466  assert(isSlice() && "Is not a slice");467  const auto dimSlices = getDimSlices();468  assert(dim < dimSlices.size() && "Dimension is out of bounds");469  return dimSlices[dim];470}471 472std::optional<uint64_t>473SparseTensorEncodingAttr::getStaticDimSliceOffset(Dimension dim) const {474  return getDimSlice(dim).getStaticOffset();475}476 477std::optional<uint64_t>478SparseTensorEncodingAttr::getStaticDimSliceStride(Dimension dim) const {479  return getDimSlice(dim).getStaticStride();480}481 482std::optional<uint64_t>483SparseTensorEncodingAttr::getStaticLvlSliceOffset(Level lvl) const {484  return getStaticDimSliceOffset(toDim(*this, lvl));485}486 487std::optional<uint64_t>488SparseTensorEncodingAttr::getStaticLvlSliceStride(Level lvl) const {489  return getStaticDimSliceStride(toDim(*this, lvl));490}491 492SmallVector<int64_t>493SparseTensorEncodingAttr::translateShape(ArrayRef<int64_t> srcShape,494                                         CrdTransDirectionKind dir) const {495  if (isIdentity())496    return SmallVector<int64_t>(srcShape);497 498  SmallVector<int64_t> ret;499  unsigned rank =500      dir == CrdTransDirectionKind::dim2lvl ? getLvlRank() : getDimRank();501  ret.reserve(rank);502 503  if (isPermutation()) {504    for (unsigned r = 0; r < rank; r++) {505      unsigned trans = dir == CrdTransDirectionKind::dim2lvl ? toDim(*this, r)506                                                             : toLvl(*this, r);507      ret.push_back(srcShape[trans]);508    }509    return ret;510  }511 512  // Handle non-permutation maps.513  AffineMap transMap =514      dir == CrdTransDirectionKind::dim2lvl ? getDimToLvl() : getLvlToDim();515 516  SmallVector<AffineExpr> dimRep;517  dimRep.reserve(srcShape.size());518  for (int64_t sz : srcShape) {519    if (ShapedType::isStatic(sz)) {520      // Push back the max coordinate for the given dimension/level size.521      dimRep.push_back(getAffineConstantExpr(sz - 1, getContext()));522    } else {523      // A dynamic size, use a AffineDimExpr to symbolize the value.524      dimRep.push_back(getAffineDimExpr(dimRep.size(), getContext()));525    }526  };527 528  for (AffineExpr exp : transMap.getResults()) {529    // Do constant propagation on the affine map.530    AffineExpr evalExp =531        simplifyAffineExpr(exp.replaceDims(dimRep), srcShape.size(), 0);532    // use llvm namespace here to avoid ambiguity533    if (auto c = llvm::dyn_cast<AffineConstantExpr>(evalExp)) {534      ret.push_back(c.getValue() + 1);535    } else {536      if (auto mod = llvm::dyn_cast<AffineBinaryOpExpr>(evalExp);537          mod && mod.getKind() == AffineExprKind::Mod) {538        // We can still infer a static bound for expressions in form539        // "d % constant" since d % constant \in [0, constant).540        if (auto bound = llvm::dyn_cast<AffineConstantExpr>(mod.getRHS())) {541          ret.push_back(bound.getValue());542          continue;543        }544      }545      ret.push_back(ShapedType::kDynamic);546    }547  }548  assert(ret.size() == rank);549  return ret;550}551 552ValueRange553SparseTensorEncodingAttr::translateCrds(OpBuilder &builder, Location loc,554                                        ValueRange crds,555                                        CrdTransDirectionKind dir) const {556  if (!getImpl())557    return crds;558 559  SmallVector<Type> retType(560      dir == CrdTransDirectionKind::lvl2dim ? getDimRank() : getLvlRank(),561      builder.getIndexType());562  auto transOp =563      CrdTranslateOp::create(builder, loc, retType, crds, dir, *this);564  return transOp.getOutCrds();565}566 567Attribute SparseTensorEncodingAttr::parse(AsmParser &parser, Type type) {568  // Open "<{" part.569  if (failed(parser.parseLess()))570    return {};571  if (failed(parser.parseLBrace()))572    return {};573 574  // Process the data from the parsed dictionary value into struct-like data.575  SmallVector<LevelType> lvlTypes;576  SmallVector<SparseTensorDimSliceAttr> dimSlices;577  AffineMap dimToLvl = {};578  AffineMap lvlToDim = {};579  unsigned posWidth = 0;580  unsigned crdWidth = 0;581  Attribute explicitVal;582  Attribute implicitVal;583  StringRef attrName;584  SmallVector<StringRef, 5> keys = {"map", "posWidth", "crdWidth",585                                    "explicitVal", "implicitVal"};586  while (succeeded(parser.parseOptionalKeyword(&attrName))) {587    // Detect admissible keyword.588    auto *it = find(keys, attrName);589    if (it == keys.end()) {590      parser.emitError(parser.getNameLoc(), "unexpected key: ") << attrName;591      return {};592    }593    unsigned keyWordIndex = it - keys.begin();594    // Consume the `=` after keys595    if (failed(parser.parseEqual()))596      return {};597    // Dispatch on keyword.598    switch (keyWordIndex) {599    case 0: { // map600      ir_detail::DimLvlMapParser cParser(parser);601      auto res = cParser.parseDimLvlMap();602      if (failed(res))603        return {};604      const auto &dlm = *res;605 606      const Level lvlRank = dlm.getLvlRank();607      for (Level lvl = 0; lvl < lvlRank; lvl++)608        lvlTypes.push_back(dlm.getLvlType(lvl));609 610      const Dimension dimRank = dlm.getDimRank();611      for (Dimension dim = 0; dim < dimRank; dim++)612        dimSlices.push_back(dlm.getDimSlice(dim));613      // NOTE: the old syntax requires an all-or-nothing approach to614      // `dimSlices`; therefore, if any slice actually exists then we need615      // to convert null-DSA into default/nop DSA.616      const auto isDefined = [](SparseTensorDimSliceAttr slice) {617        return static_cast<bool>(slice.getImpl());618      };619      if (llvm::any_of(dimSlices, isDefined)) {620        const auto defaultSlice =621            SparseTensorDimSliceAttr::get(parser.getContext());622        for (Dimension dim = 0; dim < dimRank; dim++)623          if (!isDefined(dimSlices[dim]))624            dimSlices[dim] = defaultSlice;625      } else {626        dimSlices.clear();627      }628 629      dimToLvl = dlm.getDimToLvlMap(parser.getContext());630      lvlToDim = dlm.getLvlToDimMap(parser.getContext());631      break;632    }633    case 1: { // posWidth634      Attribute attr;635      if (failed(parser.parseAttribute(attr)))636        return {};637      auto intAttr = llvm::dyn_cast<IntegerAttr>(attr);638      if (!intAttr) {639        parser.emitError(parser.getNameLoc(),640                         "expected an integral position bitwidth");641        return {};642      }643      posWidth = intAttr.getInt();644      break;645    }646    case 2: { // crdWidth647      Attribute attr;648      if (failed(parser.parseAttribute(attr)))649        return {};650      auto intAttr = llvm::dyn_cast<IntegerAttr>(attr);651      if (!intAttr) {652        parser.emitError(parser.getNameLoc(),653                         "expected an integral index bitwidth");654        return {};655      }656      crdWidth = intAttr.getInt();657      break;658    }659    case 3: { // explicitVal660      Attribute attr;661      if (failed(parser.parseAttribute(attr)))662        return {};663      if (auto result = llvm::dyn_cast<FloatAttr>(attr)) {664        explicitVal = result;665      } else if (auto result = llvm::dyn_cast<IntegerAttr>(attr)) {666        explicitVal = result;667      } else if (auto result = llvm::dyn_cast<complex::NumberAttr>(attr)) {668        explicitVal = result;669      } else {670        parser.emitError(parser.getNameLoc(),671                         "expected a numeric value for explicitVal");672        return {};673      }674      break;675    }676    case 4: { // implicitVal677      Attribute attr;678      if (failed(parser.parseAttribute(attr)))679        return {};680      if (auto result = llvm::dyn_cast<FloatAttr>(attr)) {681        implicitVal = result;682      } else if (auto result = llvm::dyn_cast<IntegerAttr>(attr)) {683        implicitVal = result;684      } else if (auto result = llvm::dyn_cast<complex::NumberAttr>(attr)) {685        implicitVal = result;686      } else {687        parser.emitError(parser.getNameLoc(),688                         "expected a numeric value for implicitVal");689        return {};690      }691      break;692    }693    } // switch694    // Only last item can omit the comma.695    if (parser.parseOptionalComma().failed())696      break;697  }698 699  // Close "}>" part.700  if (failed(parser.parseRBrace()))701    return {};702  if (failed(parser.parseGreater()))703    return {};704 705  // Construct struct-like storage for attribute.706  if (!lvlToDim || lvlToDim.isEmpty()) {707    lvlToDim = inferLvlToDim(dimToLvl, parser.getContext());708  }709  return parser.getChecked<SparseTensorEncodingAttr>(710      parser.getContext(), lvlTypes, dimToLvl, lvlToDim, posWidth, crdWidth,711      explicitVal, implicitVal, dimSlices);712}713 714void SparseTensorEncodingAttr::print(AsmPrinter &printer) const {715  auto map = static_cast<AffineMap>(getDimToLvl());716  // Empty affine map indicates identity map717  if (!map)718    map = AffineMap::getMultiDimIdentityMap(getLvlTypes().size(), getContext());719  printer << "<{ map = ";720  printSymbols(map, printer);721  printer << '(';722  printDimensions(map, printer, getDimSlices());723  printer << ") -> (";724  printLevels(map, printer, getLvlTypes());725  printer << ')';726  // Print remaining members only for non-default values.727  if (getPosWidth())728    printer << ", posWidth = " << getPosWidth();729  if (getCrdWidth())730    printer << ", crdWidth = " << getCrdWidth();731  if (getExplicitVal()) {732    printer << ", explicitVal = " << getExplicitVal();733  }734  if (getImplicitVal())735    printer << ", implicitVal = " << getImplicitVal();736  printer << " }>";737}738 739void SparseTensorEncodingAttr::printSymbols(AffineMap &map,740                                            AsmPrinter &printer) const {741  if (map.getNumSymbols() == 0)742    return;743  printer << '[';744  for (unsigned i = 0, n = map.getNumSymbols() - 1; i < n; i++)745    printer << 's' << i << ", ";746  if (map.getNumSymbols() >= 1)747    printer << 's' << map.getNumSymbols() - 1;748  printer << ']';749}750 751void SparseTensorEncodingAttr::printDimensions(752    AffineMap &map, AsmPrinter &printer,753    ArrayRef<SparseTensorDimSliceAttr> dimSlices) const {754  if (!dimSlices.empty()) {755    for (unsigned i = 0, n = map.getNumDims() - 1; i < n; i++)756      printer << 'd' << i << " : " << dimSlices[i] << ", ";757    if (map.getNumDims() >= 1) {758      printer << 'd' << map.getNumDims() - 1 << " : "759              << dimSlices[map.getNumDims() - 1];760    }761  } else {762    for (unsigned i = 0, n = map.getNumDims() - 1; i < n; i++)763      printer << 'd' << i << ", ";764    if (map.getNumDims() >= 1)765      printer << 'd' << map.getNumDims() - 1;766  }767}768 769void SparseTensorEncodingAttr::printLevels(AffineMap &map, AsmPrinter &printer,770                                           ArrayRef<LevelType> lvlTypes) const {771  for (unsigned i = 0, n = map.getNumResults() - 1; i < n; i++) {772    map.getResult(i).print(printer.getStream());773    printer << " : " << toMLIRString(lvlTypes[i]) << ", ";774  }775  if (map.getNumResults() >= 1) {776    auto lastIndex = map.getNumResults() - 1;777    map.getResult(lastIndex).print(printer.getStream());778    printer << " : " << toMLIRString(lvlTypes[lastIndex]);779  }780}781 782LogicalResult SparseTensorEncodingAttr::verify(783    function_ref<InFlightDiagnostic()> emitError, ArrayRef<LevelType> lvlTypes,784    AffineMap dimToLvl, AffineMap lvlToDim, unsigned posWidth,785    unsigned crdWidth, Attribute explicitVal, Attribute implicitVal,786    ArrayRef<SparseTensorDimSliceAttr> dimSlices) {787  if (!acceptBitWidth(posWidth))788    return emitError() << "unexpected position bitwidth: " << posWidth;789  if (!acceptBitWidth(crdWidth))790    return emitError() << "unexpected coordinate bitwidth: " << crdWidth;791 792  // Verify every COO segment.793  auto *it = llvm::find_if(lvlTypes, isSingletonLT);794  while (it != lvlTypes.end()) {795    if (it == lvlTypes.begin() ||796        !(it - 1)->isa<LevelFormat::Compressed, LevelFormat::LooseCompressed>())797      return emitError() << "expected compressed or loose_compressed level "798                            "before singleton level";799 800    auto *curCOOEnd = std::find_if_not(it, lvlTypes.end(), isSingletonLT);801    if (!std::all_of(it, curCOOEnd, isSingletonLT))802      return emitError() << "expected all singleton lvlTypes "803                            "following a singleton level";804    // We can potentially support mixed SoA/AoS singleton levels.805    if (!std::all_of(it, curCOOEnd, [it](LevelType i) {806          return it->isa<LevelPropNonDefault::SoA>() ==807                 i.isa<LevelPropNonDefault::SoA>();808        })) {809      return emitError() << "expected all singleton lvlTypes stored in the "810                            "same memory layout (SoA vs AoS).";811    }812    it = std::find_if(curCOOEnd, lvlTypes.end(), isSingletonLT);813  }814 815  auto lastBatch = std::find_if(lvlTypes.rbegin(), lvlTypes.rend(), isBatchLT);816  if (!std::all_of(lastBatch, lvlTypes.rend(), isBatchLT))817    return emitError() << "Batch lvlType can only be leading levels.";818 819  // SoA property can only be applied on singleton level.820  auto soaLvls = llvm::make_filter_range(lvlTypes, [](LevelType lt) {821    return lt.isa<LevelPropNonDefault::SoA>();822  });823  if (llvm::any_of(soaLvls, [](LevelType lt) {824        return !lt.isa<LevelFormat::Singleton>();825      })) {826    return emitError() << "SoA is only applicable to singleton lvlTypes.";827  }828 829  // TODO: audit formats that actually are supported by backend.830  if (auto it = llvm::find_if(lvlTypes, isNOutOfMLT);831      it != std::end(lvlTypes)) {832    if (it != lvlTypes.end() - 1)833      return emitError() << "expected n_out_of_m to be the last level type";834    if (!std::all_of(lvlTypes.begin(), it, isDenseLT))835      return emitError() << "expected all dense lvlTypes "836                            "before a n_out_of_m level";837    if (dimToLvl && (dimToLvl.getNumDims() != dimToLvl.getNumResults())) {838      if (!isBlockSparsity(dimToLvl)) {839        return emitError()840               << "expected 1xm block structure for n_out_of_m level";841      }842      auto sizes = getBlockSize(dimToLvl);843      unsigned coefficient = 0;844      for (const auto &elem : sizes) {845        if (elem != 0) {846          if (elem != coefficient && coefficient != 0) {847            return emitError() << "expected only one blocked level "848                                  "with the same coefficients";849          }850          coefficient = elem;851        }852      }853      if (coefficient != getM(*it)) {854        return emitError() << "expected coeffiencts of Affine expressions "855                              "to be equal to m of n_out_of_m level";856      }857    }858  }859  // Before we can check that the level-rank is consistent/coherent860  // across all fields, we need to define it.  The source-of-truth for861  // the `getLvlRank` method is the length of the level-types array,862  // since it must always be provided and have full rank; therefore we863  // use that same source-of-truth here.864  const Level lvlRank = lvlTypes.size();865  if (lvlRank == 0)866    return emitError() << "expected a non-empty array for lvlTypes";867  // We save `dimRank` here because we'll also need it to verify `dimSlices`.868  const Dimension dimRank = dimToLvl ? dimToLvl.getNumDims() : lvlRank;869  if (dimToLvl) {870    if (dimToLvl.getNumResults() != lvlRank)871      return emitError()872             << "level-rank mismatch between dimToLvl and lvlTypes: "873             << dimToLvl.getNumResults() << " != " << lvlRank;874    auto inferRes = inferLvlToDim(dimToLvl, dimToLvl.getContext());875    // Symbols can't be inferred but are acceptable.876    if (!inferRes && dimToLvl.getNumSymbols() == 0)877      return emitError() << "failed to infer lvlToDim from dimToLvl";878    if (lvlToDim && (inferRes != lvlToDim))879      return emitError() << "expected lvlToDim to be an inverse of dimToLvl";880    if (dimRank > lvlRank)881      return emitError() << "unexpected dimToLvl mapping from " << dimRank882                         << " to " << lvlRank;883  }884  if (!dimSlices.empty()) {885    if (dimSlices.size() != dimRank)886      return emitError()887             << "dimension-rank mismatch between dimSlices and dimToLvl: "888             << dimSlices.size() << " != " << dimRank;889    // Compiler support for `dimSlices` currently requires that the two890    // ranks agree.  (However, it does allow `dimToLvl` to be a permutation.)891    if (dimRank != lvlRank)892      return emitError()893             << "dimSlices expected dimension-rank to match level-rank: "894             << dimRank << " != " << lvlRank;895  }896  return success();897}898 899LogicalResult SparseTensorEncodingAttr::verifyEncoding(900    ArrayRef<Size> dimShape, Type elementType,901    function_ref<InFlightDiagnostic()> emitError) const {902  // Check structural integrity.  In particular, this ensures that the903  // level-rank is coherent across all the fields.904  if (failed(verify(emitError, getLvlTypes(), getDimToLvl(), getLvlToDim(),905                    getPosWidth(), getCrdWidth(), getExplicitVal(),906                    getImplicitVal(), getDimSlices())))907    return failure();908  // Check integrity with tensor type specifics.  In particular, we909  // need only check that the dimension-rank of the tensor agrees with910  // the dimension-rank of the encoding.911  const Dimension dimRank = dimShape.size();912  if (dimRank == 0)913    return emitError() << "expected non-scalar sparse tensor";914  if (getDimRank() != dimRank)915    return emitError()916           << "dimension-rank mismatch between encoding and tensor shape: "917           << getDimRank() << " != " << dimRank;918  if (auto expVal = getExplicitVal()) {919    Type attrType = llvm::dyn_cast<TypedAttr>(expVal).getType();920    if (attrType != elementType) {921      return emitError() << "explicit value type mismatch between encoding and "922                         << "tensor element type: " << attrType923                         << " != " << elementType;924    }925  }926  if (auto impVal = getImplicitVal()) {927    Type attrType = llvm::dyn_cast<TypedAttr>(impVal).getType();928    if (attrType != elementType) {929      return emitError() << "implicit value type mismatch between encoding and "930                         << "tensor element type: " << attrType931                         << " != " << elementType;932    }933    // Currently, we only support zero as the implicit value.934    auto impFVal = llvm::dyn_cast<FloatAttr>(impVal);935    auto impIntVal = llvm::dyn_cast<IntegerAttr>(impVal);936    auto impComplexVal = llvm::dyn_cast<complex::NumberAttr>(impVal);937    if ((impFVal && impFVal.getValue().isNonZero()) ||938        (impIntVal && !impIntVal.getValue().isZero()) ||939        (impComplexVal && (impComplexVal.getImag().isNonZero() ||940                           impComplexVal.getReal().isNonZero()))) {941      return emitError() << "implicit value must be zero";942    }943  }944  return success();945}946 947Level mlir::sparse_tensor::SparseTensorEncodingAttr::getAoSCOOStart() const {948  SmallVector<COOSegment> coo = getCOOSegments();949  assert(coo.size() == 1 || coo.empty());950  if (!coo.empty() && coo.front().isAoS()) {951    return coo.front().lvlRange.first;952  }953  return getLvlRank();954}955 956SmallVector<COOSegment>957mlir::sparse_tensor::SparseTensorEncodingAttr::getCOOSegments() const {958  SmallVector<COOSegment> ret;959  if (getLvlRank() <= 1)960    return ret;961 962  ArrayRef<LevelType> lts = getLvlTypes();963  Level l = 0;964  while (l < getLvlRank()) {965    auto lt = lts[l];966    if (lt.isa<LevelFormat::Compressed, LevelFormat::LooseCompressed>()) {967      auto cur = lts.begin() + l;968      auto end = std::find_if(cur + 1, lts.end(), [](LevelType lt) {969        return !lt.isa<LevelFormat::Singleton>();970      });971      unsigned cooLen = std::distance(cur, end);972      if (cooLen > 1) {973        // To support mixed SoA/AoS COO, we should break the segment when the974        // storage scheme changes, for now we faithfully assume that all975        // consecutive singleton levels have the same storage format as verified976        // STEA.977        ret.push_back(COOSegment{std::make_pair(l, l + cooLen),978                                 lts[l + 1].isa<LevelPropNonDefault::SoA>()});979      }980      l += cooLen;981    } else {982      l++;983    }984  }985  return ret;986}987 988//===----------------------------------------------------------------------===//989// SparseTensorType Methods.990//===----------------------------------------------------------------------===//991 992bool mlir::sparse_tensor::SparseTensorType::isCOOType(Level startLvl,993                                                      bool isUnique) const {994  if (!hasEncoding())995    return false;996  if (!isCompressedLvl(startLvl) && !isLooseCompressedLvl(startLvl))997    return false;998  for (Level l = startLvl + 1; l < lvlRank; ++l)999    if (!isSingletonLvl(l))1000      return false;1001  // If isUnique is true, then make sure that the last level is unique,1002  // that is, when lvlRank == 1, the only compressed level is unique,1003  // and when lvlRank > 1, the last singleton is unique.1004  return !isUnique || isUniqueLvl(lvlRank - 1);1005}1006 1007RankedTensorType1008mlir::sparse_tensor::SparseTensorType::getCOOType(bool ordered) const {1009  SmallVector<LevelType> lvlTypes;1010  lvlTypes.reserve(lvlRank);1011  // A non-unique compressed level at beginning (unless this is1012  // also the last level, then it is unique).1013  lvlTypes.push_back(1014      *buildLevelType(LevelFormat::Compressed, ordered, lvlRank == 1));1015  if (lvlRank > 1) {1016    // Followed by n-2 non-unique singleton levels.1017    std::fill_n(std::back_inserter(lvlTypes), lvlRank - 2,1018                *buildLevelType(LevelFormat::Singleton, ordered, false));1019    // Ends by a unique singleton level.1020    lvlTypes.push_back(*buildLevelType(LevelFormat::Singleton, ordered, true));1021  }1022  auto enc = SparseTensorEncodingAttr::get(1023      getContext(), lvlTypes, getDimToLvl(), getLvlToDim(), getPosWidth(),1024      getCrdWidth(), getExplicitVal(), getImplicitVal());1025  return RankedTensorType::get(getDimShape(), getElementType(), enc);1026}1027 1028//===----------------------------------------------------------------------===//1029// Convenience Methods.1030//===----------------------------------------------------------------------===//1031 1032SparseTensorEncodingAttr1033mlir::sparse_tensor::getSparseTensorEncoding(Type type) {1034  if (auto ttp = llvm::dyn_cast<RankedTensorType>(type))1035    return llvm::dyn_cast_or_null<SparseTensorEncodingAttr>(ttp.getEncoding());1036  if (auto mdtp = llvm::dyn_cast<StorageSpecifierType>(type))1037    return mdtp.getEncoding();1038  return nullptr;1039}1040 1041AffineMap mlir::sparse_tensor::inferLvlToDim(AffineMap dimToLvl,1042                                             MLIRContext *context) {1043  auto map = static_cast<AffineMap>(dimToLvl);1044  AffineMap lvlToDim;1045  // Return an empty lvlToDim when inference is not successful.1046  if (!map || map.getNumSymbols() != 0) {1047    lvlToDim = AffineMap();1048  } else if (map.isPermutation()) {1049    lvlToDim = inversePermutation(map);1050  } else if (isBlockSparsity(map)) {1051    lvlToDim = inverseBlockSparsity(map, context);1052  }1053  return lvlToDim;1054}1055 1056AffineMap mlir::sparse_tensor::inverseBlockSparsity(AffineMap dimToLvl,1057                                                    MLIRContext *context) {1058  SmallVector<AffineExpr> lvlExprs;1059  auto numLvls = dimToLvl.getNumResults();1060  lvlExprs.reserve(numLvls);1061  // lvlExprComponents stores information of the floordiv and mod operations1062  // applied to the same dimension, so as to build the lvlToDim map.1063  std::map<unsigned, SmallVector<AffineExpr, 3>> lvlExprComponents;1064  for (unsigned i = 0, n = numLvls; i < n; i++) {1065    auto result = dimToLvl.getResult(i);1066    if (auto binOp = dyn_cast<AffineBinaryOpExpr>(result)) {1067      if (result.getKind() == AffineExprKind::FloorDiv) {1068        // Position of the dimension in dimToLvl.1069        auto pos = dyn_cast<AffineDimExpr>(binOp.getLHS()).getPosition();1070        assert(lvlExprComponents.find(pos) == lvlExprComponents.end() &&1071               "expected only one floordiv for each dimension");1072        SmallVector<AffineExpr, 3> components;1073        // Level variable for floordiv.1074        components.push_back(getAffineDimExpr(i, context));1075        // Multiplier.1076        components.push_back(binOp.getRHS());1077        // Map key is the position of the dimension.1078        lvlExprComponents[pos] = components;1079      } else if (result.getKind() == AffineExprKind::Mod) {1080        auto pos = dyn_cast<AffineDimExpr>(binOp.getLHS()).getPosition();1081        assert(lvlExprComponents.find(pos) != lvlExprComponents.end() &&1082               "expected floordiv before mod");1083        // Add level variable for mod to the same vector1084        // of the corresponding floordiv.1085        lvlExprComponents[pos].push_back(getAffineDimExpr(i, context));1086      } else {1087        assert(false && "expected floordiv or mod");1088      }1089    } else {1090      lvlExprs.push_back(getAffineDimExpr(i, context));1091    }1092  }1093  // Build lvlExprs from lvlExprComponents.1094  // For example, for il = i floordiv 2 and ii = i mod 2, the components1095  // would be [il, 2, ii]. It could be used to build the AffineExpr1096  // i = il * 2 + ii in lvlToDim.1097  for (auto &components : lvlExprComponents) {1098    assert(components.second.size() == 3 &&1099           "expected 3 components to build lvlExprs");1100    auto mulOp = getAffineBinaryOpExpr(1101        AffineExprKind::Mul, components.second[0], components.second[1]);1102    auto addOp =1103        getAffineBinaryOpExpr(AffineExprKind::Add, mulOp, components.second[2]);1104    lvlExprs.push_back(addOp);1105  }1106  return dimToLvl.get(dimToLvl.getNumResults(), 0, lvlExprs, context);1107}1108 1109SmallVector<unsigned> mlir::sparse_tensor::getBlockSize(AffineMap dimToLvl) {1110  assert(isBlockSparsity(dimToLvl) &&1111         "expected dimToLvl to be block sparsity for calling getBlockSize");1112  SmallVector<unsigned> blockSize;1113  for (auto result : dimToLvl.getResults()) {1114    if (auto binOp = dyn_cast<AffineBinaryOpExpr>(result)) {1115      if (result.getKind() == AffineExprKind::Mod) {1116        blockSize.push_back(1117            dyn_cast<AffineConstantExpr>(binOp.getRHS()).getValue());1118      }1119    } else {1120      blockSize.push_back(0);1121    }1122  }1123  return blockSize;1124}1125 1126bool mlir::sparse_tensor::isBlockSparsity(AffineMap dimToLvl) {1127  if (!dimToLvl)1128    return false;1129  std::map<unsigned, int64_t> coeffientMap;1130  bool hasBlock = false;1131  for (auto result : dimToLvl.getResults()) {1132    if (auto binOp = dyn_cast<AffineBinaryOpExpr>(result)) {1133      // Check for "dim op const".1134      auto dimOp = dyn_cast<AffineDimExpr>(binOp.getLHS());1135      auto conOp = dyn_cast<AffineConstantExpr>(binOp.getRHS());1136      if (!dimOp || !conOp || conOp.getValue() <= 0)1137        return false;1138      // Inspect "dim / const" or "dim % const".1139      auto pos = dimOp.getPosition();1140      if (binOp.getKind() == AffineExprKind::FloorDiv) {1141        // Expect only one floordiv for each dimension.1142        auto [it, inserted] = coeffientMap.try_emplace(pos);1143        if (!inserted)1144          return false;1145        // Record coefficient of the floordiv.1146        it->second = conOp.getValue();1147      } else if (binOp.getKind() == AffineExprKind::Mod) {1148        // Expect floordiv before mod.1149        auto it = coeffientMap.find(pos);1150        if (it == coeffientMap.end())1151          return false;1152        // Expect mod to have the same coefficient as floordiv.1153        if (conOp.getValue() != it->second)1154          return false;1155        hasBlock = true;1156      } else {1157        return false;1158      }1159    } else if (auto dimOp = dyn_cast<AffineDimExpr>(result)) {1160      auto pos = dimOp.getPosition();1161      // Expect dim to be unset.1162      if (!coeffientMap.try_emplace(pos, 0).second)1163        return false;1164    } else {1165      return false;1166    }1167  }1168  return hasBlock;1169}1170 1171bool mlir::sparse_tensor::hasAnyNonIdentityOperandsOrResults(Operation *op) {1172  auto hasNonIdentityMap = [](Value v) {1173    auto stt = tryGetSparseTensorType(v);1174    return stt && !stt->isIdentity();1175  };1176 1177  return llvm::any_of(op->getOperands(), hasNonIdentityMap) ||1178         llvm::any_of(op->getResults(), hasNonIdentityMap);1179}1180 1181Dimension mlir::sparse_tensor::toDim(SparseTensorEncodingAttr enc, Level l) {1182  if (enc) {1183    assert(enc.isPermutation() && "Non permutation map not supported");1184    if (const auto dimToLvl = enc.getDimToLvl())1185      return dimToLvl.getDimPosition(l);1186  }1187  return l;1188}1189 1190Level mlir::sparse_tensor::toLvl(SparseTensorEncodingAttr enc, Dimension d) {1191  if (enc) {1192    assert(enc.isPermutation() && "Non permutation map not supported");1193    if (const auto lvlToDim = enc.getLvlToDim())1194      return lvlToDim.getDimPosition(d);1195  }1196  return d;1197}1198 1199/// We normalized sparse tensor encoding attribute by always using1200/// ordered/unique LT such that "compressed_nu_no" and "compressed_nu" (as well1201/// as other variants) lead to the same storage specifier type, and stripping1202/// irrelevant fields that do not alter the sparse tensor memory layout.1203static SparseTensorEncodingAttr1204getNormalizedEncodingForSpecifier(SparseTensorEncodingAttr enc) {1205  SmallVector<LevelType> lts;1206  for (auto lt : enc.getLvlTypes())1207    lts.push_back(lt.stripStorageIrrelevantProperties());1208 1209  return SparseTensorEncodingAttr::get(1210      enc.getContext(), lts,1211      AffineMap(), // dimToLvl (irrelevant to storage specifier)1212      AffineMap(), // lvlToDim (irrelevant to storage specifier)1213      // Always use `index` for memSize and lvlSize instead of reusing1214      // `getPosWidth` and `getCrdWidth`. It allows us to reuse the same SSA1215      // value for different bitwidth, it also avoids casting between index and1216      // integer (returned by DimOp)1217      0, 0,1218      Attribute(), // explicitVal (irrelevant to storage specifier)1219      Attribute(), // implicitVal (irrelevant to storage specifier)1220      enc.getDimSlices());1221}1222 1223StorageSpecifierType1224StorageSpecifierType::get(MLIRContext *ctx, SparseTensorEncodingAttr encoding) {1225  return Base::get(ctx, getNormalizedEncodingForSpecifier(encoding));1226}1227 1228StorageSpecifierType1229StorageSpecifierType::getChecked(function_ref<InFlightDiagnostic()> emitError,1230                                 MLIRContext *ctx,1231                                 SparseTensorEncodingAttr encoding) {1232  return Base::getChecked(emitError, ctx,1233                          getNormalizedEncodingForSpecifier(encoding));1234}1235 1236//===----------------------------------------------------------------------===//1237// SparseTensorDialect Operations.1238//===----------------------------------------------------------------------===//1239 1240static LogicalResult lvlIsInBounds(Level lvl, Value tensor) {1241  return success(lvl < getSparseTensorType(tensor).getLvlRank());1242}1243 1244static LogicalResult isMatchingWidth(Value mem, unsigned width) {1245  const Type etp = getMemRefType(mem).getElementType();1246  return success(width == 0 ? etp.isIndex() : etp.isInteger(width));1247}1248 1249static LogicalResult verifySparsifierGetterSetter(1250    StorageSpecifierKind mdKind, std::optional<Level> lvl,1251    TypedValue<StorageSpecifierType> md, Operation *op) {1252  if (mdKind == StorageSpecifierKind::ValMemSize && lvl) {1253    return op->emitError(1254        "redundant level argument for querying value memory size");1255  }1256 1257  const auto enc = md.getType().getEncoding();1258  const Level lvlRank = enc.getLvlRank();1259 1260  if (mdKind == StorageSpecifierKind::DimOffset ||1261      mdKind == StorageSpecifierKind::DimStride)1262    if (!enc.isSlice())1263      return op->emitError("requested slice data on non-slice tensor");1264 1265  if (mdKind != StorageSpecifierKind::ValMemSize) {1266    if (!lvl)1267      return op->emitError("missing level argument");1268 1269    const Level l = lvl.value();1270    if (l >= lvlRank)1271      return op->emitError("requested level is out of bounds");1272 1273    if (mdKind == StorageSpecifierKind::PosMemSize && enc.isSingletonLvl(l))1274      return op->emitError(1275          "requested position memory size on a singleton level");1276  }1277  return success();1278}1279 1280static Type getFieldElemType(SparseTensorType stt, SparseTensorFieldKind kind) {1281  switch (kind) {1282  case SparseTensorFieldKind::CrdMemRef:1283    return stt.getCrdType();1284  case SparseTensorFieldKind::PosMemRef:1285    return stt.getPosType();1286  case SparseTensorFieldKind::ValMemRef:1287    return stt.getElementType();1288  case SparseTensorFieldKind::StorageSpec:1289    return nullptr;1290  }1291  llvm_unreachable("Unrecognizable FieldKind");1292}1293 1294static LogicalResult verifyPackUnPack(Operation *op, bool requiresStaticShape,1295                                      SparseTensorType stt,1296                                      RankedTensorType valTp,1297                                      TypeRange lvlTps) {1298  if (requiresStaticShape && !stt.hasStaticDimShape())1299    return op->emitError("the sparse-tensor must have static shape");1300  if (!stt.hasEncoding())1301    return op->emitError("the sparse-tensor must have an encoding attribute");1302 1303  // Verifies the trailing COO.1304  Level cooStartLvl = stt.getAoSCOOStart();1305  if (cooStartLvl < stt.getLvlRank()) {1306    // We only supports trailing COO for now, must be the last input.1307    auto cooTp = llvm::cast<ShapedType>(lvlTps.back());1308    // The coordinates should be in shape of <? x rank>1309    unsigned expCOORank = stt.getLvlRank() - cooStartLvl;1310    if (cooTp.getRank() != 2 || expCOORank != cooTp.getShape().back()) {1311      return op->emitError("input/output trailing COO level-ranks don't match");1312    }1313  }1314 1315  // Verifies that all types match.1316  StorageLayout layout(stt.getEncoding());1317  if (layout.getNumDataFields() != lvlTps.size() + 1) // plus one value memref1318    return op->emitError("inconsistent number of fields between input/output");1319 1320  unsigned idx = 0;1321  bool misMatch = false;1322  layout.foreachField([&idx, &misMatch, stt, valTp,1323                       lvlTps](FieldIndex fid, SparseTensorFieldKind fKind,1324                               Level lvl, LevelType lt) -> bool {1325    if (fKind == SparseTensorFieldKind::StorageSpec)1326      return true;1327 1328    Type inputTp = nullptr;1329    if (fKind == SparseTensorFieldKind::ValMemRef) {1330      inputTp = valTp;1331    } else {1332      assert(fid == idx && stt.getLvlType(lvl) == lt);1333      inputTp = lvlTps[idx++];1334    }1335    // The input element type and expected element type should match.1336    Type inpElemTp = llvm::cast<TensorType>(inputTp).getElementType();1337    Type expElemTp = getFieldElemType(stt, fKind);1338    if (inpElemTp != expElemTp) {1339      misMatch = true;1340      return false; // to terminate the iteration1341    }1342    return true;1343  });1344 1345  if (misMatch)1346    return op->emitError("input/output element-types don't match");1347  return success();1348}1349 1350LogicalResult AssembleOp::verify() {1351  RankedTensorType valuesTp = getValues().getType();1352  const auto lvlsTp = getLevels().getTypes();1353  const auto resTp = getSparseTensorType(getResult());1354  return verifyPackUnPack(*this, true, resTp, valuesTp, lvlsTp);1355}1356 1357LogicalResult DisassembleOp::verify() {1358  if (getOutValues().getType() != getRetValues().getType())1359    return emitError("output values and return value type mismatch");1360 1361  for (auto [ot, rt] : llvm::zip_equal(getOutLevels(), getRetLevels()))1362    if (ot.getType() != rt.getType())1363      return emitError("output levels and return levels type mismatch");1364 1365  RankedTensorType valuesTp = getRetValues().getType();1366  const auto lvlsTp = getRetLevels().getTypes();1367  const auto srcTp = getSparseTensorType(getTensor());1368  return verifyPackUnPack(*this, false, srcTp, valuesTp, lvlsTp);1369}1370 1371LogicalResult ConvertOp::verify() {1372  RankedTensorType tp1 = getSource().getType();1373  RankedTensorType tp2 = getDest().getType();1374  if (tp1.getRank() != tp2.getRank())1375    return emitError("unexpected conversion mismatch in rank");1376  auto dstEnc =1377      llvm::dyn_cast_or_null<SparseTensorEncodingAttr>(tp2.getEncoding());1378  if (dstEnc && dstEnc.isSlice())1379    return emitError("cannot convert to a sparse tensor slice");1380 1381  auto shape1 = tp1.getShape();1382  auto shape2 = tp2.getShape();1383  // Accept size matches between the source and the destination type1384  // (e.g. 10 vs. 10, 10 vs. ?, or ? vs. ?), but reject direct mismatches or1385  // matches that would need a runtime assert (e.g. 10 vs. 20 or ? vs. 10).1386  for (Dimension d = 0, dimRank = tp1.getRank(); d < dimRank; d++)1387    if (shape1[d] != shape2[d] && shape2[d] != ShapedType::kDynamic)1388      return emitError("unexpected conversion mismatch in dimension ") << d;1389  return success();1390}1391 1392OpFoldResult ConvertOp::fold(FoldAdaptor adaptor) {1393  if (getType() == getSource().getType())1394    return getSource();1395  return {};1396}1397 1398bool ConvertOp::needsExtraSort() {1399  SparseTensorType srcStt = getSparseTensorType(getSource());1400  SparseTensorType dstStt = getSparseTensorType(getDest());1401 1402  // We do not need an extra sort when returning unordered sparse tensors or1403  // dense tensor since dense tensor support random access.1404  if (dstStt.isAllDense() || !dstStt.isAllOrdered())1405    return false;1406 1407  if (srcStt.isAllOrdered() && dstStt.isAllOrdered() &&1408      srcStt.hasSameDimToLvl(dstStt)) {1409    return false;1410  }1411 1412  // Source and dest tensors are ordered in different ways. We only do direct1413  // dense to sparse conversion when the dense input is defined by a sparse1414  // constant. Note that we can theoretically always directly convert from dense1415  // inputs by rotating dense loops but it leads to bad cache locality and hurt1416  // performance.1417  if (auto constOp = getSource().getDefiningOp<arith::ConstantOp>())1418    if (isa<SparseElementsAttr>(constOp.getValue()))1419      return false;1420 1421  return true;1422}1423 1424LogicalResult CrdTranslateOp::verify() {1425  uint64_t inRank = getEncoder().getLvlRank();1426  uint64_t outRank = getEncoder().getDimRank();1427 1428  if (getDirection() == CrdTransDirectionKind::dim2lvl)1429    std::swap(inRank, outRank);1430 1431  if (inRank != getInCrds().size() || outRank != getOutCrds().size())1432    return emitError("Coordinate rank mismatch with encoding");1433 1434  return success();1435}1436 1437LogicalResult CrdTranslateOp::fold(FoldAdaptor adaptor,1438                                   SmallVectorImpl<OpFoldResult> &results) {1439  if (getEncoder().isIdentity()) {1440    results.assign(getInCrds().begin(), getInCrds().end());1441    return success();1442  }1443  if (getEncoder().isPermutation()) {1444    AffineMap perm = getDirection() == CrdTransDirectionKind::dim2lvl1445                         ? getEncoder().getDimToLvl()1446                         : getEncoder().getLvlToDim();1447    for (AffineExpr exp : perm.getResults())1448      results.push_back(getInCrds()[cast<AffineDimExpr>(exp).getPosition()]);1449    return success();1450  }1451 1452  // Fuse dim2lvl/lvl2dim pairs.1453  auto def = getInCrds()[0].getDefiningOp<CrdTranslateOp>();1454  bool sameDef = def && llvm::all_of(getInCrds(), [def](Value v) {1455                   return v.getDefiningOp() == def;1456                 });1457  if (!sameDef)1458    return failure();1459 1460  bool oppositeDir = def.getDirection() != getDirection();1461  bool sameOracle =1462      def.getEncoder().getDimToLvl() == getEncoder().getDimToLvl();1463  bool sameCount = def.getNumResults() == getInCrds().size();1464  if (!oppositeDir || !sameOracle || !sameCount)1465    return failure();1466 1467  // The definition produces the coordinates in the same order as the input1468  // coordinates.1469  bool sameOrder = llvm::all_of(llvm::zip_equal(def.getOutCrds(), getInCrds()),1470                                [](auto valuePair) {1471                                  auto [lhs, rhs] = valuePair;1472                                  return lhs == rhs;1473                                });1474 1475  if (!sameOrder)1476    return failure();1477  // l1 = dim2lvl (lvl2dim l0)1478  // ==> l01479  results.append(def.getInCrds().begin(), def.getInCrds().end());1480  return success();1481}1482 1483void LvlOp::build(OpBuilder &builder, OperationState &state, Value source,1484                  int64_t index) {1485  Value val = arith::ConstantIndexOp::create(builder, state.location, index);1486  return build(builder, state, source, val);1487}1488 1489LogicalResult LvlOp::verify() {1490  if (std::optional<uint64_t> lvl = getConstantLvlIndex()) {1491    auto stt = getSparseTensorType(getSource());1492    if (static_cast<uint64_t>(lvl.value()) >= stt.getLvlRank())1493      return emitError(1494          "Level index exceeds the rank of the input sparse tensor");1495  }1496  return success();1497}1498 1499std::optional<uint64_t> LvlOp::getConstantLvlIndex() {1500  return getConstantIntValue(getIndex());1501}1502 1503Speculation::Speculatability LvlOp::getSpeculatability() {1504  auto constantIndex = getConstantLvlIndex();1505  if (!constantIndex)1506    return Speculation::NotSpeculatable;1507 1508  assert(constantIndex <1509         cast<RankedTensorType>(getSource().getType()).getRank());1510  return Speculation::Speculatable;1511}1512 1513OpFoldResult LvlOp::fold(FoldAdaptor adaptor) {1514  auto lvlIndex = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getIndex());1515  if (!lvlIndex)1516    return {};1517 1518  Level lvl = lvlIndex.getAPSInt().getZExtValue();1519  auto stt = getSparseTensorType(getSource());1520  if (lvl >= stt.getLvlRank()) {1521    // Follows the same convention used by tensor.dim operation. Out of bound1522    // indices produce undefined behavior but are still valid IR. Don't choke on1523    // them.1524    return {};1525  }1526 1527  // Helper lambda to build an IndexAttr.1528  auto getIndexAttr = [this](int64_t lvlSz) {1529    return IntegerAttr::get(IndexType::get(getContext()), APInt(64, lvlSz));1530  };1531 1532  SmallVector<Size> lvlShape = stt.getLvlShape();1533  if (ShapedType::isStatic(lvlShape[lvl]))1534    return getIndexAttr(lvlShape[lvl]);1535 1536  return {};1537}1538 1539void ReinterpretMapOp::build(OpBuilder &odsBuilder, OperationState &odsState,1540                             SparseTensorEncodingAttr dstEnc, Value source) {1541  auto srcStt = getSparseTensorType(source);1542  SmallVector<int64_t> srcLvlShape = srcStt.getLvlShape();1543  SmallVector<int64_t> dstDimShape =1544      dstEnc.translateShape(srcLvlShape, CrdTransDirectionKind::lvl2dim);1545  auto dstTp =1546      RankedTensorType::get(dstDimShape, srcStt.getElementType(), dstEnc);1547  return build(odsBuilder, odsState, dstTp, source);1548}1549 1550LogicalResult ReinterpretMapOp::verify() {1551  auto srcStt = getSparseTensorType(getSource());1552  auto dstStt = getSparseTensorType(getDest());1553  ArrayRef<LevelType> srcLvlTps = srcStt.getLvlTypes();1554  ArrayRef<LevelType> dstLvlTps = dstStt.getLvlTypes();1555 1556  if (srcLvlTps.size() != dstLvlTps.size())1557    return emitError("Level rank mismatch between source/dest tensors");1558 1559  for (auto [srcLvlTp, dstLvlTp] : llvm::zip(srcLvlTps, dstLvlTps))1560    if (srcLvlTp != dstLvlTp)1561      return emitError("Level type mismatch between source/dest tensors");1562 1563  if (srcStt.getPosWidth() != dstStt.getPosWidth() ||1564      srcStt.getCrdWidth() != dstStt.getCrdWidth()) {1565    return emitError("Crd/Pos width mismatch between source/dest tensors");1566  }1567 1568  if (srcStt.getElementType() != dstStt.getElementType())1569    return emitError("Element type mismatch between source/dest tensors");1570 1571  SmallVector<Size> srcLvlShape = srcStt.getLvlShape();1572  SmallVector<Size> dstLvlShape = dstStt.getLvlShape();1573  for (auto [srcLvlSz, dstLvlSz] : llvm::zip(srcLvlShape, dstLvlShape)) {1574    if (srcLvlSz != dstLvlSz) {1575      // Should we allow one side to be dynamic size, e.g., <?x?> should be1576      // compatible to <3x4>? For now, we require all the level sizes to be1577      // *exactly* matched for simplicity.1578      return emitError("Level size mismatch between source/dest tensors");1579    }1580  }1581 1582  return success();1583}1584 1585OpFoldResult ReinterpretMapOp::fold(FoldAdaptor adaptor) {1586  if (getSource().getType() == getDest().getType())1587    return getSource();1588 1589  if (auto def = getSource().getDefiningOp<ReinterpretMapOp>()) {1590    // A -> B, B -> A ==> A1591    if (def.getSource().getType() == getDest().getType())1592      return def.getSource();1593  }1594  return {};1595}1596 1597template <typename ToBufferOp>1598static LogicalResult inferSparseBufferType(ValueRange ops, DictionaryAttr attr,1599                                           OpaqueProperties prop,1600                                           RegionRange region,1601                                           SmallVectorImpl<mlir::Type> &ret) {1602  typename ToBufferOp::Adaptor adaptor(ops, attr, prop, region);1603  SparseTensorType stt = getSparseTensorType(adaptor.getTensor());1604  Type elemTp = nullptr;1605  bool withStride = false;1606  if constexpr (std::is_same_v<ToBufferOp, ToPositionsOp>) {1607    elemTp = stt.getPosType();1608  } else if constexpr (std::is_same_v<ToBufferOp, ToCoordinatesOp> ||1609                       std::is_same_v<ToBufferOp, ToCoordinatesBufferOp>) {1610    elemTp = stt.getCrdType();1611    if constexpr (std::is_same_v<ToBufferOp, ToCoordinatesOp>)1612      withStride = stt.getAoSCOOStart() <= adaptor.getLevel();1613  } else if constexpr (std::is_same_v<ToBufferOp, ToValuesOp>) {1614    elemTp = stt.getElementType();1615  }1616 1617  assert(elemTp && "unhandled operation.");1618  SmallVector<int64_t> bufShape = stt.getBatchLvlShape();1619  bufShape.push_back(ShapedType::kDynamic);1620 1621  auto layout = withStride ? StridedLayoutAttr::StridedLayoutAttr::get(1622                                 stt.getContext(), ShapedType::kDynamic,1623                                 {ShapedType::kDynamic})1624                           : StridedLayoutAttr();1625  ret.emplace_back(MemRefType::get(bufShape, elemTp, layout));1626  return success();1627}1628 1629LogicalResult ToPositionsOp::verify() {1630  auto stt = getSparseTensorType(getTensor());1631  if (failed(lvlIsInBounds(getLevel(), getTensor())))1632    return emitError("requested level is out of bounds");1633  if (failed(isMatchingWidth(getResult(), stt.getPosWidth())))1634    return emitError("unexpected type for positions");1635  return success();1636}1637 1638LogicalResult1639ToPositionsOp::inferReturnTypes(MLIRContext *ctx, std::optional<Location> loc,1640                                ValueRange ops, DictionaryAttr attr,1641                                OpaqueProperties prop, RegionRange region,1642                                SmallVectorImpl<mlir::Type> &ret) {1643  return inferSparseBufferType<ToPositionsOp>(ops, attr, prop, region, ret);1644}1645 1646LogicalResult ToCoordinatesOp::verify() {1647  auto stt = getSparseTensorType(getTensor());1648  if (failed(lvlIsInBounds(getLevel(), getTensor())))1649    return emitError("requested level is out of bounds");1650  if (failed(isMatchingWidth(getResult(), stt.getCrdWidth())))1651    return emitError("unexpected type for coordinates");1652  return success();1653}1654 1655LogicalResult1656ToCoordinatesOp::inferReturnTypes(MLIRContext *ctx, std::optional<Location> loc,1657                                  ValueRange ops, DictionaryAttr attr,1658                                  OpaqueProperties prop, RegionRange region,1659                                  SmallVectorImpl<mlir::Type> &ret) {1660  return inferSparseBufferType<ToCoordinatesOp>(ops, attr, prop, region, ret);1661}1662 1663LogicalResult ToCoordinatesBufferOp::verify() {1664  auto stt = getSparseTensorType(getTensor());1665  if (stt.getAoSCOOStart() >= stt.getLvlRank())1666    return emitError("expected sparse tensor with a COO region");1667  return success();1668}1669 1670LogicalResult ToCoordinatesBufferOp::inferReturnTypes(1671    MLIRContext *ctx, std::optional<Location> loc, ValueRange ops,1672    DictionaryAttr attr, OpaqueProperties prop, RegionRange region,1673    SmallVectorImpl<mlir::Type> &ret) {1674  return inferSparseBufferType<ToCoordinatesBufferOp>(ops, attr, prop, region,1675                                                      ret);1676}1677 1678LogicalResult ToValuesOp::verify() {1679  auto stt = getSparseTensorType(getTensor());1680  auto mtp = getMemRefType(getResult());1681  if (stt.getElementType() != mtp.getElementType())1682    return emitError("unexpected mismatch in element types");1683  return success();1684}1685 1686LogicalResult ToValuesOp::inferReturnTypes(MLIRContext *ctx,1687                                           std::optional<Location> loc,1688                                           ValueRange ops, DictionaryAttr attr,1689                                           OpaqueProperties prop,1690                                           RegionRange region,1691                                           SmallVectorImpl<mlir::Type> &ret) {1692  return inferSparseBufferType<ToValuesOp>(ops, attr, prop, region, ret);1693}1694 1695LogicalResult ToSliceOffsetOp::verify() {1696  auto rank = getSlice().getType().getRank();1697  if (rank <= getDim().getSExtValue() || getDim().getSExtValue() < 0)1698    return emitError("requested dimension out of bound");1699  return success();1700}1701 1702LogicalResult ToSliceStrideOp::verify() {1703  auto rank = getSlice().getType().getRank();1704  if (rank <= getDim().getSExtValue() || getDim().getSExtValue() < 0)1705    return emitError("requested dimension out of bound");1706  return success();1707}1708 1709LogicalResult GetStorageSpecifierOp::verify() {1710  return verifySparsifierGetterSetter(getSpecifierKind(), getLevel(),1711                                      getSpecifier(), getOperation());1712}1713 1714template <typename SpecifierOp>1715static SetStorageSpecifierOp getSpecifierSetDef(SpecifierOp op) {1716  return op.getSpecifier().template getDefiningOp<SetStorageSpecifierOp>();1717}1718 1719OpFoldResult GetStorageSpecifierOp::fold(FoldAdaptor adaptor) {1720  const StorageSpecifierKind kind = getSpecifierKind();1721  const auto lvl = getLevel();1722  for (auto op = getSpecifierSetDef(*this); op; op = getSpecifierSetDef(op))1723    if (kind == op.getSpecifierKind() && lvl == op.getLevel())1724      return op.getValue();1725  return {};1726}1727 1728LogicalResult SetStorageSpecifierOp::verify() {1729  return verifySparsifierGetterSetter(getSpecifierKind(), getLevel(),1730                                      getSpecifier(), getOperation());1731}1732 1733template <class T>1734static LogicalResult verifyNumBlockArgs(T *op, Region &region,1735                                        const char *regionName,1736                                        TypeRange inputTypes, Type outputType) {1737  unsigned numArgs = region.getNumArguments();1738  unsigned expectedNum = inputTypes.size();1739  if (numArgs != expectedNum)1740    return op->emitError() << regionName << " region must have exactly "1741                           << expectedNum << " arguments";1742 1743  for (unsigned i = 0; i < numArgs; i++) {1744    Type typ = region.getArgument(i).getType();1745    if (typ != inputTypes[i])1746      return op->emitError() << regionName << " region argument " << (i + 1)1747                             << " type mismatch";1748  }1749  Operation *term = region.front().getTerminator();1750  YieldOp yield = dyn_cast<YieldOp>(term);1751  if (!yield)1752    return op->emitError() << regionName1753                           << " region must end with sparse_tensor.yield";1754  if (!yield.hasSingleResult() ||1755      yield.getSingleResult().getType() != outputType)1756    return op->emitError() << regionName << " region yield type mismatch";1757 1758  return success();1759}1760 1761LogicalResult BinaryOp::verify() {1762  NamedAttrList attrs = (*this)->getAttrs();1763  Type leftType = getX().getType();1764  Type rightType = getY().getType();1765  Type outputType = getOutput().getType();1766  Region &overlap = getOverlapRegion();1767  Region &left = getLeftRegion();1768  Region &right = getRightRegion();1769 1770  // Check correct number of block arguments and return type for each1771  // non-empty region.1772  if (!overlap.empty()) {1773    if (failed(verifyNumBlockArgs(this, overlap, "overlap",1774                                  TypeRange{leftType, rightType}, outputType)))1775      return failure();1776  }1777  if (!left.empty()) {1778    if (failed(verifyNumBlockArgs(this, left, "left", TypeRange{leftType},1779                                  outputType)))1780      return failure();1781  } else if (getLeftIdentity()) {1782    if (leftType != outputType)1783      return emitError("left=identity requires first argument to have the same "1784                       "type as the output");1785  }1786  if (!right.empty()) {1787    if (failed(verifyNumBlockArgs(this, right, "right", TypeRange{rightType},1788                                  outputType)))1789      return failure();1790  } else if (getRightIdentity()) {1791    if (rightType != outputType)1792      return emitError("right=identity requires second argument to have the "1793                       "same type as the output");1794  }1795  return success();1796}1797 1798LogicalResult UnaryOp::verify() {1799  Type inputType = getX().getType();1800  Type outputType = getOutput().getType();1801 1802  // Check correct number of block arguments and return type for each1803  // non-empty region.1804  Region &present = getPresentRegion();1805  if (!present.empty()) {1806    if (failed(verifyNumBlockArgs(this, present, "present",1807                                  TypeRange{inputType}, outputType)))1808      return failure();1809  }1810  Region &absent = getAbsentRegion();1811  if (!absent.empty()) {1812    if (failed(verifyNumBlockArgs(this, absent, "absent", TypeRange{},1813                                  outputType)))1814      return failure();1815    // Absent branch can only yield invariant values.1816    Block *absentBlock = &absent.front();1817    Block *parent = getOperation()->getBlock();1818    Value absentVal =1819        cast<YieldOp>(absentBlock->getTerminator()).getSingleResult();1820    if (auto arg = dyn_cast<BlockArgument>(absentVal)) {1821      if (arg.getOwner() == parent)1822        return emitError("absent region cannot yield linalg argument");1823    } else if (Operation *def = absentVal.getDefiningOp()) {1824      if (!isa<arith::ConstantOp>(def) &&1825          (def->getBlock() == absentBlock || def->getBlock() == parent))1826        return emitError("absent region cannot yield locally computed value");1827    }1828  }1829  return success();1830}1831 1832bool ConcatenateOp::needsExtraSort() {1833  SparseTensorType dstStt = getSparseTensorType(*this);1834  if (dstStt.isAllDense() || !dstStt.isAllOrdered())1835    return false;1836 1837  bool allSameOrdered = llvm::all_of(getInputs(), [dstStt](Value op) {1838    return getSparseTensorType(op).hasSameDimToLvl(dstStt);1839  });1840  // TODO: When conDim != 0, as long as conDim corresponding to the first level1841  // in all input/output buffers, and all input/output buffers have the same1842  // dimToLvl, the tmp COO buffer is still unnecessary (e.g, concatenate1843  // CSC matrices along column).1844  bool directLowerable =1845      allSameOrdered && getDimension() == 0 && dstStt.isIdentity();1846  return !directLowerable;1847}1848 1849LogicalResult ConcatenateOp::verify() {1850  const auto dstTp = getSparseTensorType(*this);1851  const Dimension concatDim = getDimension();1852  const Dimension dimRank = dstTp.getDimRank();1853 1854  if (getInputs().size() <= 1)1855    return emitError("Need at least two tensors to concatenate.");1856 1857  if (concatDim >= dimRank)1858    return emitError(llvm::formatv(1859        "Concat-dimension is out of bounds for dimension-rank ({0} >= {1})",1860        concatDim, dimRank));1861 1862  for (const auto &it : llvm::enumerate(getInputs())) {1863    const auto i = it.index();1864    const auto srcTp = getSparseTensorType(it.value());1865    if (srcTp.hasDynamicDimShape())1866      return emitError(llvm::formatv("Input tensor ${0} has dynamic shape", i));1867    const Dimension srcDimRank = srcTp.getDimRank();1868    if (srcDimRank != dimRank)1869      return emitError(1870          llvm::formatv("Input tensor ${0} has a different rank (rank={1}) "1871                        "from the output tensor (rank={2}).",1872                        i, srcDimRank, dimRank));1873  }1874 1875  for (Dimension d = 0; d < dimRank; d++) {1876    const Size dstSh = dstTp.getDimShape()[d];1877    if (d == concatDim) {1878      if (ShapedType::isStatic(dstSh)) {1879        // If we reach here, then all inputs have static shapes.  So we1880        // can use `getDimShape()[d]` instead of `*getDynamicDimSize(d)`1881        // to avoid redundant assertions in the loop.1882        Size sumSz = 0;1883        for (const auto src : getInputs())1884          sumSz += getSparseTensorType(src).getDimShape()[d];1885        // If all dimension are statically known, the sum of all the input1886        // dimensions should be equal to the output dimension.1887        if (sumSz != dstSh)1888          return emitError(1889              "The concatenation dimension of the output tensor should be the "1890              "sum of all the concatenation dimensions of the input tensors.");1891      }1892    } else {1893      Size prev = dstSh;1894      for (const auto src : getInputs()) {1895        const auto sh = getSparseTensorType(src).getDimShape()[d];1896        if (ShapedType::isStatic(prev) && sh != prev)1897          return emitError("All dimensions (expect for the concatenating one) "1898                           "should be equal.");1899        prev = sh;1900      }1901    }1902  }1903 1904  return success();1905}1906 1907void PushBackOp::build(OpBuilder &builder, OperationState &result,1908                       Value curSize, Value inBuffer, Value value) {1909  build(builder, result, curSize, inBuffer, value, Value());1910}1911 1912LogicalResult PushBackOp::verify() {1913  if (Value n = getN()) {1914    std::optional<int64_t> nValue = getConstantIntValue(n);1915    if (nValue && nValue.value() < 1)1916      return emitOpError("n must be not less than 1");1917  }1918  return success();1919}1920 1921LogicalResult CompressOp::verify() {1922  const auto stt = getSparseTensorType(getTensor());1923  if (stt.getLvlRank() != 1 + static_cast<Level>(getLvlCoords().size()))1924    return emitOpError("incorrect number of coordinates");1925  return success();1926}1927 1928void ForeachOp::build(1929    OpBuilder &builder, OperationState &result, Value tensor,1930    ValueRange initArgs, AffineMapAttr order,1931    function_ref<void(OpBuilder &, Location, ValueRange, Value, ValueRange)>1932        bodyBuilder) {1933  build(builder, result, initArgs.getTypes(), tensor, initArgs, order);1934  // Builds foreach body.1935  if (!bodyBuilder)1936    return;1937  const auto stt = getSparseTensorType(tensor);1938  const Dimension dimRank = stt.getDimRank();1939 1940  // Starts with `dimRank`-many coordinates.1941  SmallVector<Type> blockArgTypes(dimRank, builder.getIndexType());1942  // Followed by one value.1943  blockArgTypes.push_back(stt.getElementType());1944  // Followed by the reduction variables.1945  blockArgTypes.append(initArgs.getTypes().begin(), initArgs.getTypes().end());1946 1947  SmallVector<Location> blockArgLocs(blockArgTypes.size(), tensor.getLoc());1948 1949  OpBuilder::InsertionGuard guard(builder);1950  auto &region = *result.regions.front();1951  Block *bodyBlock =1952      builder.createBlock(&region, region.end(), blockArgTypes, blockArgLocs);1953  bodyBuilder(builder, result.location,1954              bodyBlock->getArguments().slice(0, dimRank),1955              bodyBlock->getArguments()[dimRank],1956              bodyBlock->getArguments().drop_front(dimRank + 1));1957}1958 1959LogicalResult ForeachOp::verify() {1960  const auto t = getSparseTensorType(getTensor());1961  const Dimension dimRank = t.getDimRank();1962  const auto args = getBody()->getArguments();1963 1964  if (getOrder().has_value() && getOrder()->getNumDims() != t.getLvlRank())1965    return emitError("Level traverse order does not match tensor's level rank");1966 1967  if (dimRank + 1 + getInitArgs().size() != args.size())1968    return emitError("Unmatched number of arguments in the block");1969 1970  if (getNumResults() != getInitArgs().size())1971    return emitError("Mismatch in number of init arguments and results");1972 1973  if (getResultTypes() != getInitArgs().getTypes())1974    return emitError("Mismatch in types of init arguments and results");1975 1976  // Cannot mark this const, because the getters aren't.1977  auto yield = cast<YieldOp>(getBody()->getTerminator());1978  if (yield.getNumOperands() != getNumResults() ||1979      yield.getOperands().getTypes() != getResultTypes())1980    return emitError("Mismatch in types of yield values and results");1981 1982  const auto iTp = IndexType::get(getContext());1983  for (Dimension d = 0; d < dimRank; d++)1984    if (args[d].getType() != iTp)1985      return emitError(1986          llvm::formatv("Expecting Index type for argument at index {0}", d));1987 1988  const auto elemTp = t.getElementType();1989  const auto valueTp = args[dimRank].getType();1990  if (elemTp != valueTp)1991    return emitError(1992        llvm::formatv("Unmatched element type between input tensor and "1993                      "block argument, expected:{0}, got: {1}",1994                      elemTp, valueTp));1995  return success();1996}1997 1998OpFoldResult ReorderCOOOp::fold(FoldAdaptor adaptor) {1999  if (getSparseTensorEncoding(getInputCoo().getType()) ==2000      getSparseTensorEncoding(getResultCoo().getType()))2001    return getInputCoo();2002 2003  return {};2004}2005 2006LogicalResult ReorderCOOOp::verify() {2007  SparseTensorType srcStt = getSparseTensorType(getInputCoo());2008  SparseTensorType dstStt = getSparseTensorType(getResultCoo());2009 2010  if (!srcStt.isCOOType() || !dstStt.isCOOType())2011    return emitError("Expected COO sparse tensors only");2012 2013  if (!srcStt.hasSameDimToLvl(dstStt))2014    return emitError("Unmatched dim2lvl map between input and result COO");2015 2016  if (srcStt.getPosType() != dstStt.getPosType() ||2017      srcStt.getCrdType() != dstStt.getCrdType() ||2018      srcStt.getElementType() != dstStt.getElementType())2019    return emitError("Unmatched storage format between input and result COO");2020 2021  return success();2022}2023 2024LogicalResult ReduceOp::verify() {2025  Type inputType = getX().getType();2026  Region &formula = getRegion();2027  return verifyNumBlockArgs(this, formula, "reduce",2028                            TypeRange{inputType, inputType}, inputType);2029}2030 2031LogicalResult SelectOp::verify() {2032  Builder b(getContext());2033  Type inputType = getX().getType();2034  Type boolType = b.getI1Type();2035  Region &formula = getRegion();2036  return verifyNumBlockArgs(this, formula, "select", TypeRange{inputType},2037                            boolType);2038}2039 2040LogicalResult SortOp::verify() {2041  AffineMap xPerm = getPermMap();2042  uint64_t nx = xPerm.getNumDims();2043  if (nx < 1)2044    return emitError(llvm::formatv("Expected rank(perm_map) > 1, got {0}", nx));2045 2046  if (!xPerm.isPermutation())2047    return emitError(2048        llvm::formatv("Expected a permutation map, got {0}", xPerm));2049 2050  // We can't check the size of the buffers when n or buffer dimensions aren't2051  // compile-time constants.2052  std::optional<int64_t> cn = getConstantIntValue(getN());2053  if (!cn)2054    return success();2055 2056  // Verify dimensions.2057  const auto checkDim = [&](Value v, Size minSize,2058                            const char *message) -> LogicalResult {2059    const Size sh = getMemRefType(v).getShape()[0];2060    if (ShapedType::isStatic(sh) && sh < minSize)2061      return emitError(2062          llvm::formatv("{0} got {1} < {2}", message, sh, minSize));2063    return success();2064  };2065  uint64_t n = cn.value();2066  uint64_t ny = 0;2067  if (auto nyAttr = getNyAttr())2068    ny = nyAttr.getInt();2069  if (failed(checkDim(getXy(), n * (nx + ny),2070                      "Expected dimension(xy) >= n * (rank(perm_map) + ny)")))2071    return failure();2072  for (Value opnd : getYs())2073    if (failed(checkDim(opnd, n, "Expected dimension(y) >= n")))2074      return failure();2075 2076  return success();2077}2078 2079//===----------------------------------------------------------------------===//2080// Sparse Tensor Iteration Operations.2081//===----------------------------------------------------------------------===//2082 2083IterSpaceType IteratorType::getIterSpaceType() const {2084  return IterSpaceType::get(getContext(), getEncoding(), getLoLvl(),2085                            getHiLvl());2086}2087 2088IteratorType IterSpaceType::getIteratorType() const {2089  return IteratorType::get(getContext(), getEncoding(), getLoLvl(), getHiLvl());2090}2091 2092/// Parses a level range in the form "$lo `to` $hi"2093/// or simply "$lo" if $hi - $lo = 12094static ParseResult parseLevelRange(AsmParser &parser, Level &lvlLo,2095                                   Level &lvlHi) {2096  if (parser.parseInteger(lvlLo))2097    return failure();2098 2099  if (succeeded(parser.parseOptionalKeyword("to"))) {2100    if (parser.parseInteger(lvlHi))2101      return failure();2102  } else {2103    lvlHi = lvlLo + 1;2104  }2105 2106  if (lvlHi <= lvlLo)2107    return parser.emitError(parser.getNameLoc(),2108                            "expect larger level upper bound than lower bound");2109 2110  return success();2111}2112 2113/// Parses a level range in the form "$lo `to` $hi"2114/// or simply "$lo" if $hi - $lo = 12115static ParseResult parseLevelRange(OpAsmParser &parser, IntegerAttr &lvlLoAttr,2116                                   IntegerAttr &lvlHiAttr) {2117  Level lvlLo, lvlHi;2118  if (parseLevelRange(parser, lvlLo, lvlHi))2119    return failure();2120 2121  lvlLoAttr = IntegerAttr::get(parser.getBuilder().getIndexType(), lvlLo);2122  lvlHiAttr = IntegerAttr::get(parser.getBuilder().getIndexType(), lvlHi);2123  return success();2124}2125 2126/// Prints a level range in the form "$lo `to` $hi"2127/// or simply "$lo" if $hi - $lo = 12128static void printLevelRange(AsmPrinter &p, Level lo, Level hi) {2129 2130  if (lo + 1 == hi)2131    p << lo;2132  else2133    p << lo << " to " << hi;2134}2135 2136/// Prints a level range in the form "$lo `to` $hi"2137/// or simply "$lo" if $hi - $lo = 12138static void printLevelRange(OpAsmPrinter &p, Operation *, IntegerAttr lvlLo,2139                            IntegerAttr lvlHi) {2140  unsigned lo = lvlLo.getValue().getZExtValue();2141  unsigned hi = lvlHi.getValue().getZExtValue();2142  printLevelRange(p, lo, hi);2143}2144 2145/// Parses a list of `optional` defined list in the form of2146/// "(%val0, _, %val1, ...)", where `_` is used to annotate that the2147/// corresponding value is not defined (e.g., to represent an undefined2148/// coordinate in the sparse iteration space).2149static ParseResult parseOptionalDefinedList(2150    OpAsmParser &parser, OperationState &state, I64BitSet &definedSet,2151    SmallVectorImpl<OpAsmParser::Argument> &definedArgs,2152    unsigned maxCnt = std::numeric_limits<unsigned>::max(),2153    OpAsmParser::Delimiter delimiter = OpAsmParser::Delimiter::Paren) {2154  unsigned cnt = 0;2155  ParseResult crdList =2156      parser.parseCommaSeparatedList(delimiter, [&]() -> ParseResult {2157        if (parser.parseOptionalKeyword("_")) {2158          if (parser.parseArgument(definedArgs.emplace_back()))2159            return failure();2160          definedSet.set(cnt);2161        }2162        cnt += 1;2163        return success();2164      });2165 2166  if (cnt > maxCnt)2167    return parser.emitError(parser.getNameLoc(),2168                            "parsed more value than expected.");2169 2170  if (failed(crdList)) {2171    return parser.emitError(2172        parser.getNameLoc(),2173        "expecting SSA value or \"_\" for level coordinates");2174  }2175  assert(definedArgs.size() == definedSet.count());2176  return success();2177}2178 2179static void printOptionalDefinedList(OpAsmPrinter &p, unsigned size,2180                                     Block::BlockArgListType blocksArgs,2181                                     I64BitSet definedSet) {2182  if (definedSet.empty())2183    return;2184 2185  for (unsigned i = 0; i < size; i++) {2186    if (definedSet[i]) {2187      p << blocksArgs.front();2188      blocksArgs = blocksArgs.drop_front();2189    } else {2190      p << "_";2191    }2192    if (i != size - 1)2193      p << ", ";2194  }2195  assert(blocksArgs.empty());2196}2197 2198static ParseResult2199parseUsedCoordList(OpAsmParser &parser, OperationState &state,2200                   SmallVectorImpl<OpAsmParser::Argument> &coords) {2201  // Parse "at(%crd0, _, ...)"2202  I64BitSet crdUsedLvlSet;2203  if (succeeded(parser.parseOptionalKeyword("at")) &&2204      failed(parseOptionalDefinedList(parser, state, crdUsedLvlSet, coords)))2205    return failure();2206 2207  // Always use IndexType for the coordinate.2208  for (auto &coord : coords)2209    coord.type = parser.getBuilder().getIndexType();2210 2211  // Set the CrdUsedLvl bitset.2212  state.addAttribute("crdUsedLvls",2213                     parser.getBuilder().getI64IntegerAttr(crdUsedLvlSet));2214  return success();2215}2216 2217static ParseResult2218parseSparseIterateLoop(OpAsmParser &parser, OperationState &state,2219                       SmallVectorImpl<OpAsmParser::Argument> &iterators,2220                       SmallVectorImpl<OpAsmParser::Argument> &blockArgs) {2221  SmallVector<OpAsmParser::UnresolvedOperand> spaces;2222  SmallVector<OpAsmParser::UnresolvedOperand> initArgs;2223 2224  // Parse "%iters, ... in %spaces, ..."2225  if (parser.parseArgumentList(iterators) || parser.parseKeyword("in") ||2226      parser.parseOperandList(spaces))2227    return failure();2228 2229  if (iterators.size() != spaces.size())2230    return parser.emitError(2231        parser.getNameLoc(),2232        "mismatch in number of sparse iterators and sparse spaces");2233 2234  SmallVector<OpAsmParser::Argument> coords;2235  if (failed(parseUsedCoordList(parser, state, coords)))2236    return failure();2237  size_t numCrds = coords.size();2238 2239  // Parse "iter_args(%arg = %init, ...)"2240  bool hasIterArgs = succeeded(parser.parseOptionalKeyword("iter_args"));2241  if (hasIterArgs)2242    if (parser.parseAssignmentList(blockArgs, initArgs))2243      return failure();2244 2245  blockArgs.append(coords);2246 2247  SmallVector<Type> iterSpaceTps;2248  // parse ": sparse_tensor.iter_space -> ret"2249  if (parser.parseColon() || parser.parseTypeList(iterSpaceTps))2250    return failure();2251  if (iterSpaceTps.size() != spaces.size())2252    return parser.emitError(parser.getNameLoc(),2253                            "mismatch in number of iteration space operands "2254                            "and iteration space types");2255 2256  for (auto [it, tp] : llvm::zip_equal(iterators, iterSpaceTps)) {2257    IterSpaceType spaceTp = llvm::dyn_cast<IterSpaceType>(tp);2258    if (!spaceTp)2259      return parser.emitError(parser.getNameLoc(),2260                              "expected sparse_tensor.iter_space type for "2261                              "iteration space operands");2262    it.type = spaceTp.getIteratorType();2263  }2264 2265  if (hasIterArgs)2266    if (parser.parseArrowTypeList(state.types))2267      return failure();2268 2269  // Resolves input operands.2270  if (parser.resolveOperands(spaces, iterSpaceTps, parser.getNameLoc(),2271                             state.operands))2272    return failure();2273 2274  if (hasIterArgs) {2275    // Strip off leading args that used for coordinates.2276    MutableArrayRef args = MutableArrayRef(blockArgs).drop_back(numCrds);2277    if (args.size() != initArgs.size() || args.size() != state.types.size()) {2278      return parser.emitError(2279          parser.getNameLoc(),2280          "mismatch in number of iteration arguments and return values");2281    }2282 2283    for (auto [it, init, tp] : llvm::zip_equal(args, initArgs, state.types)) {2284      it.type = tp;2285      if (parser.resolveOperand(init, tp, state.operands))2286        return failure();2287    }2288  }2289  return success();2290}2291 2292static ParseResult2293parseSparseCoIterateLoop(OpAsmParser &parser, OperationState &state,2294                         SmallVectorImpl<Value> &spacesVals,2295                         SmallVectorImpl<OpAsmParser::Argument> &blockArgs) {2296 2297  // Parse "(%spaces, ...)"2298  SmallVector<OpAsmParser::UnresolvedOperand> spaces;2299  if (parser.parseOperandList(spaces, OpAsmParser::Delimiter::Paren))2300    return failure();2301 2302  SmallVector<OpAsmParser::Argument> coords;2303  if (failed(parseUsedCoordList(parser, state, coords)))2304    return failure();2305  size_t numCrds = coords.size();2306 2307  // Parse "iter_args(%arg = %init, ...)"2308  SmallVector<OpAsmParser::UnresolvedOperand> initArgs;2309  bool hasIterArgs = succeeded(parser.parseOptionalKeyword("iter_args"));2310  if (hasIterArgs)2311    if (parser.parseAssignmentList(blockArgs, initArgs))2312      return failure();2313  blockArgs.append(coords);2314 2315  SmallVector<Type> iterSpaceTps;2316  // parse ": (sparse_tensor.iter_space, ...) -> ret"2317  if (parser.parseColon() || parser.parseLParen() ||2318      parser.parseTypeList(iterSpaceTps) || parser.parseRParen())2319    return failure();2320 2321  if (iterSpaceTps.size() != spaces.size())2322    return parser.emitError(parser.getNameLoc(),2323                            "mismatch in number of iteration space operands "2324                            "and iteration space types");2325 2326  if (hasIterArgs)2327    if (parser.parseArrowTypeList(state.types))2328      return failure();2329 2330  // Resolves input sparse iteration spaces.2331  if (parser.resolveOperands(spaces, iterSpaceTps, parser.getNameLoc(),2332                             spacesVals))2333    return failure();2334  state.operands.append(spacesVals);2335 2336  if (hasIterArgs) {2337    // Strip off trailing args that used for coordinates.2338    MutableArrayRef args = MutableArrayRef(blockArgs).drop_back(numCrds);2339    if (args.size() != initArgs.size() || args.size() != state.types.size()) {2340      return parser.emitError(2341          parser.getNameLoc(),2342          "mismatch in number of iteration arguments and return values");2343    }2344 2345    for (auto [it, init, tp] : llvm::zip_equal(args, initArgs, state.types)) {2346      it.type = tp;2347      if (parser.resolveOperand(init, tp, state.operands))2348        return failure();2349    }2350  }2351  return success();2352}2353 2354LogicalResult ExtractIterSpaceOp::inferReturnTypes(2355    MLIRContext *ctx, std::optional<Location> loc, ValueRange ops,2356    DictionaryAttr attr, OpaqueProperties prop, RegionRange region,2357    SmallVectorImpl<mlir::Type> &ret) {2358 2359  ExtractIterSpaceOp::Adaptor adaptor(ops, attr, prop, region);2360  SparseTensorType stt = getSparseTensorType(adaptor.getTensor());2361  ret.push_back(IterSpaceType::get(ctx, stt.getEncoding(), adaptor.getLoLvl(),2362                                   adaptor.getHiLvl()));2363  return success();2364}2365 2366LogicalResult ExtractIterSpaceOp::verify() {2367  if (getLoLvl() >= getHiLvl())2368    return emitOpError("expected smaller level low than level high");2369 2370  TypedValue<IteratorType> pIter = getParentIter();2371  if ((pIter && getLoLvl() == 0) || (!pIter && getLoLvl() != 0)) {2372    return emitOpError(2373        "parent iterator should be specified iff level lower bound equals 0");2374  }2375 2376  if (pIter) {2377    IterSpaceType spaceTp = getExtractedSpace().getType();2378    if (pIter.getType().getEncoding() != spaceTp.getEncoding())2379      return emitOpError(2380          "mismatch in parent iterator encoding and iteration space encoding.");2381 2382    if (spaceTp.getLoLvl() != pIter.getType().getHiLvl())2383      return emitOpError("parent iterator should be used to extract an "2384                         "iteration space from a consecutive level.");2385  }2386 2387  return success();2388}2389 2390LogicalResult ExtractValOp::verify() {2391  auto stt = getSparseTensorType(getTensor());2392  auto itTp = getIterator().getType();2393 2394  if (stt.getEncoding() != itTp.getEncoding())2395    return emitOpError("mismatch in tensor encoding and iterator encoding.");2396 2397  if (stt.getLvlRank() != itTp.getHiLvl())2398    return emitOpError("must use last-level iterator to extract values. ");2399 2400  return success();2401}2402 2403struct RemoveUnusedLvlCrds : public OpRewritePattern<IterateOp> {2404  using OpRewritePattern::OpRewritePattern;2405 2406  LogicalResult matchAndRewrite(IterateOp iterateOp,2407                                PatternRewriter &rewriter) const override {2408    I64BitSet newUsedLvls(0);2409    llvm::BitVector toRemove(iterateOp.getBody()->getNumArguments());2410    for (unsigned i = 0, e = iterateOp.getSpaceDim(); i < e; i++) {2411      if (auto crd = iterateOp.getLvlCrd(i)) {2412        if (crd->getUsers().empty())2413          toRemove.set(crd->getArgNumber());2414        else2415          newUsedLvls.set(i);2416      }2417    }2418 2419    // All coordinates are used.2420    if (toRemove.none())2421      return failure();2422 2423    rewriter.startOpModification(iterateOp);2424    iterateOp.setCrdUsedLvls(newUsedLvls);2425    iterateOp.getBody()->eraseArguments(toRemove);2426    rewriter.finalizeOpModification(iterateOp);2427    return success();2428  }2429};2430 2431void IterateOp::getCanonicalizationPatterns(mlir::RewritePatternSet &results,2432                                            mlir::MLIRContext *context) {2433  results.add<RemoveUnusedLvlCrds>(context);2434}2435 2436void IterateOp::build(OpBuilder &builder, OperationState &odsState,2437                      Value iterSpace, ValueRange initArgs) {2438  unsigned rank = llvm::cast<IterSpaceType>(iterSpace.getType()).getSpaceDim();2439  // All ones.2440  I64BitSet set((1 << rank) - 1);2441  return build(builder, odsState, iterSpace, initArgs, set);2442}2443 2444void IterateOp::build(OpBuilder &builder, OperationState &odsState,2445                      Value iterSpace, ValueRange initArgs,2446                      I64BitSet crdUsedLvls) {2447  OpBuilder::InsertionGuard guard(builder);2448 2449  odsState.addOperands(iterSpace);2450  odsState.addOperands(initArgs);2451  odsState.getOrAddProperties<Properties>().crdUsedLvls =2452      builder.getIntegerAttr(builder.getIntegerType(64), crdUsedLvls);2453  Region *bodyRegion = odsState.addRegion();2454  odsState.addTypes(initArgs.getTypes());2455  Block *bodyBlock = builder.createBlock(bodyRegion);2456 2457  // Starts with a list of user-provided loop arguments.2458  for (Value v : initArgs)2459    bodyBlock->addArgument(v.getType(), v.getLoc());2460 2461  // Follows by a list of used coordinates.2462  for (unsigned i = 0, e = crdUsedLvls.count(); i < e; i++)2463    bodyBlock->addArgument(builder.getIndexType(), odsState.location);2464 2465  // Ends with sparse iterator2466  bodyBlock->addArgument(2467      llvm::cast<IterSpaceType>(iterSpace.getType()).getIteratorType(),2468      odsState.location);2469}2470 2471ParseResult IterateOp::parse(OpAsmParser &parser, OperationState &result) {2472  OpAsmParser::Argument iterator;2473  OpAsmParser::UnresolvedOperand iterSpace;2474 2475  SmallVector<OpAsmParser::Argument> iters, iterArgs;2476  if (parseSparseIterateLoop(parser, result, iters, iterArgs))2477    return failure();2478  if (iters.size() != 1)2479    return parser.emitError(parser.getNameLoc(),2480                            "expected only one iterator/iteration space");2481 2482  iterArgs.append(iters);2483  Region *body = result.addRegion();2484  if (parser.parseRegion(*body, iterArgs))2485    return failure();2486 2487  IterateOp::ensureTerminator(*body, parser.getBuilder(), result.location);2488 2489  // Parse the optional attribute list.2490  if (parser.parseOptionalAttrDict(result.attributes))2491    return failure();2492 2493  return success();2494}2495 2496/// Prints the initialization list in the form of2497///   <prefix>(%inner = %outer, %inner2 = %outer2, <...>)2498/// where 'inner' values are assumed to be region arguments and 'outer' values2499/// are regular SSA values.2500static void printInitializationList(OpAsmPrinter &p,2501                                    Block::BlockArgListType blocksArgs,2502                                    ValueRange initializers,2503                                    StringRef prefix = "") {2504  assert(blocksArgs.size() == initializers.size() &&2505         "expected same length of arguments and initializers");2506  if (initializers.empty())2507    return;2508 2509  p << prefix << '(';2510  llvm::interleaveComma(llvm::zip(blocksArgs, initializers), p, [&](auto it) {2511    p << std::get<0>(it) << " = " << std::get<1>(it);2512  });2513  p << ")";2514}2515 2516template <typename SparseLoopOp>2517static LogicalResult verifySparseLoopOp(SparseLoopOp op) {2518  if (op.getInitArgs().size() != op.getNumResults()) {2519    return op.emitOpError(2520        "mismatch in number of loop-carried values and defined values");2521  }2522  if (op.getCrdUsedLvls().max() > op.getSpaceDim())2523    return op.emitOpError("required out-of-bound coordinates");2524 2525  return success();2526}2527 2528LogicalResult IterateOp::verify() { return verifySparseLoopOp(*this); }2529LogicalResult CoIterateOp::verify() { return verifySparseLoopOp(*this); }2530 2531void IterateOp::print(OpAsmPrinter &p) {2532  p << " " << getIterator() << " in " << getIterSpace();2533  if (!getCrdUsedLvls().empty()) {2534    p << " at(";2535    printOptionalDefinedList(p, getSpaceDim(), getCrds(), getCrdUsedLvls());2536    p << ")";2537  }2538  printInitializationList(p, getRegionIterArgs(), getInitArgs(), " iter_args");2539 2540  p << " : " << getIterSpace().getType() << " ";2541  if (!getInitArgs().empty())2542    p.printArrowTypeList(getInitArgs().getTypes());2543 2544  p << " ";2545  p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,2546                /*printBlockTerminators=*/!getInitArgs().empty());2547}2548 2549LogicalResult IterateOp::verifyRegions() {2550  if (getIterator().getType() != getIterSpace().getType().getIteratorType())2551    return emitOpError("mismatch in iterator and iteration space type");2552  if (getNumRegionIterArgs() != getNumResults())2553    return emitOpError(2554        "mismatch in number of basic block args and defined values");2555 2556  auto initArgs = getInitArgs();2557  auto iterArgs = getRegionIterArgs();2558  auto yieldVals = getYieldedValues();2559  auto opResults = getResults();2560  if (!llvm::all_equal({initArgs.size(), iterArgs.size(), yieldVals.size(),2561                        opResults.size()})) {2562    return emitOpError() << "number mismatch between iter args and results.";2563  }2564 2565  for (auto [i, init, iter, yield, ret] :2566       llvm::enumerate(initArgs, iterArgs, yieldVals, opResults)) {2567    if (init.getType() != ret.getType())2568      return emitOpError() << "types mismatch between " << i2569                           << "th iter operand and defined value";2570    if (iter.getType() != ret.getType())2571      return emitOpError() << "types mismatch between " << i2572                           << "th iter region arg and defined value";2573    if (yield.getType() != ret.getType())2574      return emitOpError() << "types mismatch between " << i2575                           << "th yield value and defined value";2576  }2577 2578  return success();2579}2580 2581/// OpInterfaces' methods implemented by IterateOp.2582SmallVector<Region *> IterateOp::getLoopRegions() { return {&getRegion()}; }2583 2584MutableArrayRef<OpOperand> IterateOp::getInitsMutable() {2585  return getInitArgsMutable();2586}2587 2588Block::BlockArgListType IterateOp::getRegionIterArgs() {2589  return getRegion().getArguments().take_front(getNumRegionIterArgs());2590}2591 2592std::optional<MutableArrayRef<OpOperand>> IterateOp::getYieldedValuesMutable() {2593  return cast<sparse_tensor::YieldOp>(2594             getRegion().getBlocks().front().getTerminator())2595      .getResultsMutable();2596}2597 2598std::optional<ResultRange> IterateOp::getLoopResults() { return getResults(); }2599 2600OperandRange IterateOp::getEntrySuccessorOperands(RegionSuccessor successor) {2601  return getInitArgs();2602}2603 2604void IterateOp::getSuccessorRegions(RegionBranchPoint point,2605                                    SmallVectorImpl<RegionSuccessor> &regions) {2606  // Both the operation itself and the region may be branching into the body2607  // or back into the operation itself.2608  regions.push_back(RegionSuccessor(&getRegion(), getRegionIterArgs()));2609  // It is possible for loop not to enter the body.2610  regions.push_back(RegionSuccessor(getOperation(), getResults()));2611}2612 2613void CoIterateOp::build(OpBuilder &builder, OperationState &odsState,2614                        ValueRange iterSpaces, ValueRange initArgs,2615                        unsigned numCases) {2616  unsigned rank =2617      cast<IterSpaceType>(iterSpaces.front().getType()).getSpaceDim();2618  // All ones.2619  I64BitSet set((1 << rank) - 1);2620  // Generates all-zero case bits (they only serve as placeholders), which are2621  // supposed to be overriden later. We need to preallocate all the regions as2622  // mlir::Region cannot be dynamically added later after the operation is2623  // created.2624  SmallVector<int64_t> caseBits(numCases, 0);2625  ArrayAttr cases = builder.getI64ArrayAttr(caseBits);2626  return CoIterateOp::build(builder, odsState, initArgs.getTypes(), iterSpaces,2627                            initArgs, set, cases,2628                            /*caseRegionsCount=*/numCases);2629}2630 2631ParseResult CoIterateOp::parse(OpAsmParser &parser, OperationState &result) {2632 2633  SmallVector<Value> spaces;2634  // The block argument list of each regions, it is arranged in the order of2635  // ([used coordinate list], [loop iterations args], [sparse iterator list]).2636  SmallVector<OpAsmParser::Argument> blockArgs;2637  if (parseSparseCoIterateLoop(parser, result, spaces, blockArgs))2638    return failure();2639 2640  result.addAttribute("operandSegmentSizes",2641                      parser.getBuilder().getDenseI32ArrayAttr(2642                          {static_cast<int32_t>(spaces.size()),2643                           static_cast<int32_t>(result.types.size())}));2644 2645  SmallVector<Attribute> cases;2646  while (succeeded(parser.parseOptionalKeyword("case"))) {2647    // Parse one region per case.2648    I64BitSet definedItSet;2649    SmallVector<OpAsmParser::Argument> definedIts;2650    if (parseOptionalDefinedList(parser, result, definedItSet, definedIts,2651                                 spaces.size(), OpAsmParser::Delimiter::None))2652      return failure();2653 2654    cases.push_back(parser.getBuilder().getI64IntegerAttr(definedItSet));2655 2656    for (auto [i, definedIdx] : llvm::enumerate(definedItSet.bits())) {2657      // Resolve the iterator type based on the iteration space type.2658      auto spaceTp = llvm::cast<IterSpaceType>(spaces[definedIdx].getType());2659      definedIts[i].type = spaceTp.getIteratorType();2660    }2661    definedIts.insert(definedIts.begin(), blockArgs.begin(), blockArgs.end());2662    Region *body = result.addRegion();2663    if (parser.parseRegion(*body, definedIts))2664      return failure();2665 2666    CoIterateOp::ensureTerminator(*body, parser.getBuilder(), result.location);2667  }2668 2669  result.addAttribute("cases", ArrayAttr::get(parser.getContext(), cases));2670 2671  // Parse the optional attribute list.2672  if (parser.parseOptionalAttrDict(result.attributes))2673    return failure();2674 2675  return success();2676}2677 2678void CoIterateOp::print(OpAsmPrinter &p) {2679  p << " (";2680  llvm::interleaveComma(getIterSpaces(), p, [&](auto s) { p << s; });2681  p << ")";2682 2683  if (!getCrdUsedLvls().empty()) {2684    p << " at(";2685    printOptionalDefinedList(p, getSpaceDim(), getCrds(0), getCrdUsedLvls());2686    p << ")";2687  }2688 2689  printInitializationList(p, getRegionIterArgs(0), getInitArgs(), " iter_args");2690 2691  p << " : (" << getIterSpaces().getTypes() << ")";2692  if (!getInitArgs().empty())2693    p.printArrowTypeList(getInitArgs().getTypes());2694 2695  for (unsigned idx = 0, e = getRegions().size(); idx < e; idx++) {2696    p.printNewline();2697    p << "case ";2698    printOptionalDefinedList(p, getIterSpaces().size(), getRegionIterators(idx),2699                             getRegionDefinedSpace(idx));2700    p << " ";2701    p.printRegion(getRegion(idx), /*printEntryBlockArgs=*/false,2702                  /*printBlockTerminators=*/!getInitArgs().empty());2703  }2704}2705 2706ValueRange CoIterateOp::getYieldedValues(unsigned regionIdx) {2707  return cast<sparse_tensor::YieldOp>(2708             getRegion(regionIdx).getBlocks().front().getTerminator())2709      .getResults();2710}2711 2712LogicalResult CoIterateOp::verifyRegions() {2713  for (unsigned r = 0, e = getNumRegions(); r < e; r++) {2714    if (getNumRegionIterArgs() != getNumResults())2715      return emitOpError(2716          "mismatch in number of basic block args and defined values");2717 2718    auto initArgs = getInitArgs();2719    auto iterArgs = getRegionIterArgs(r);2720    auto yieldVals = getYieldedValues(r);2721    auto opResults = getResults();2722    if (!llvm::all_equal({initArgs.size(), iterArgs.size(), yieldVals.size(),2723                          opResults.size()})) {2724      return emitOpError()2725             << "number mismatch between iter args and results on " << r2726             << "th region";2727    }2728 2729    for (auto [i, init, iter, yield, ret] :2730         llvm::enumerate(initArgs, iterArgs, yieldVals, opResults)) {2731      if (init.getType() != ret.getType())2732        return emitOpError()2733               << "types mismatch between " << i2734               << "th iter operand and defined value on " << r << "th region";2735      if (iter.getType() != ret.getType())2736        return emitOpError() << "types mismatch between " << i2737                             << "th iter region arg and defined value on " << r2738                             << "th region";2739      if (yield.getType() != ret.getType())2740        return emitOpError()2741               << "types mismatch between " << i2742               << "th yield value and defined value on " << r << "th region";2743    }2744  }2745 2746  auto cases = getRegionDefinedSpaces();2747  llvm::SmallSetVector<uint64_t, 8> set(cases.begin(), cases.end());2748  if (set.size() != getNumRegions())2749    return emitOpError("contains duplicated cases.");2750 2751  return success();2752}2753 2754SmallVector<Region *> CoIterateOp::getSubCasesOf(unsigned regionIdx) {2755  SmallVector<Region *> ret;2756  I64BitSet caseBit = getRegionDefinedSpace(regionIdx);2757  for (Region &r : getCaseRegions())2758    if (getRegionDefinedSpace(r.getRegionNumber()).isSubSetOf(caseBit))2759      ret.push_back(&r);2760 2761  return ret;2762}2763 2764//===----------------------------------------------------------------------===//2765// Sparse Tensor Dialect Setups.2766//===----------------------------------------------------------------------===//2767 2768/// Materialize a single constant operation from a given attribute value with2769/// the desired resultant type.2770Operation *SparseTensorDialect::materializeConstant(OpBuilder &builder,2771                                                    Attribute value, Type type,2772                                                    Location loc) {2773  if (auto op = arith::ConstantOp::materialize(builder, value, type, loc))2774    return op;2775  return nullptr;2776}2777 2778void SparseTensorDialect::initialize() {2779  addAttributes<2780#define GET_ATTRDEF_LIST2781#include "mlir/Dialect/SparseTensor/IR/SparseTensorAttrDefs.cpp.inc"2782      >();2783  addTypes<2784#define GET_TYPEDEF_LIST2785#include "mlir/Dialect/SparseTensor/IR/SparseTensorTypes.cpp.inc"2786      >();2787  addOperations<2788#define GET_OP_LIST2789#include "mlir/Dialect/SparseTensor/IR/SparseTensorOps.cpp.inc"2790      >();2791  declarePromisedInterfaces<2792      bufferization::BufferizableOpInterface, ConcatenateOp, ConvertOp, LoadOp,2793      NewOp, NumberOfEntriesOp, AssembleOp, DisassembleOp,2794      ToCoordinatesBufferOp, ToCoordinatesOp, ToPositionsOp, ToValuesOp>();2795}2796 2797#define GET_OP_CLASSES2798#include "mlir/Dialect/SparseTensor/IR/SparseTensorOps.cpp.inc"2799 2800#include "mlir/Dialect/SparseTensor/IR/SparseTensorOpsDialect.cpp.inc"2801