diff --git a/mlir/lib/ExecutionEngine/SparseTensorUtils.cpp b/mlir/lib/ExecutionEngine/SparseTensorUtils.cpp --- a/mlir/lib/ExecutionEngine/SparseTensorUtils.cpp +++ b/mlir/lib/ExecutionEngine/SparseTensorUtils.cpp @@ -72,6 +72,15 @@ static constexpr int kColWidth = 1025; +/// A version of `operator*=` which checks for overflows. +/// +/// Precondition: `lhs` must not be zero. +static inline void checkedMulAssign(uint64_t &lhs, uint64_t rhs) { + assert(rhs <= std::numeric_limits::max() / lhs && + "Integer overflow"); + lhs *= rhs; +} + /// A sparse tensor element in coordinate scheme (value and indices). /// For example, a rank-1 vector element would look like /// ({i}, a[i]) @@ -260,7 +269,7 @@ uint64_t sz = 1; for (uint64_t r = 0; r < rank; r++) { assert(sizes[r] > 0 && "Dimension size zero has trivial storage"); - sz *= sizes[r]; + checkedMulAssign(sz, sizes[r]); if (sparsity[r] == DimLevelType::kCompressed) { pointers[r].reserve(sz + 1); indices[r].reserve(sz); @@ -358,7 +367,7 @@ /// Finalizes lexicographic insertions. void endInsert() override { if (values.empty()) - endDim(0); + finalizeSegment(0); else endPath(0); } @@ -412,23 +421,47 @@ } private: - /// Appends the next free position of `indices[d]` to `pointers[d]`. - /// Thus, when called after inserting the last element of a segment, - /// it will append the position where the next segment begins. - inline void appendPointer(uint64_t d) { - assert(isCompressedDim(d)); // Entails `d < getRank()`. - uint64_t p = indices[d].size(); - assert(p <= std::numeric_limits

::max() && + /// Appends an arbitrary new position to `pointers[d]`. This method + /// checks that `pos` is representable in the `P` type; however, it + /// does not check that `pos` is semantically valid (i.e., larger than + /// the previous position and smaller than `indices[d].capacity()`). + inline void appendPointer(uint64_t d, uint64_t pos, uint64_t count = 1) { + assert(isCompressedDim(d)); + assert(pos <= std::numeric_limits

::max() && "Pointer value is too large for the P-type"); - pointers[d].push_back(p); // Here is where we convert to `P`. + pointers[d].insert(pointers[d].end(), count, static_cast

(pos)); } - /// Appends the given index to `indices[d]`. - inline void appendIndex(uint64_t d, uint64_t i) { - assert(isCompressedDim(d)); // Entails `d < getRank()`. - assert(i <= std::numeric_limits::max() && - "Index value is too large for the I-type"); - indices[d].push_back(i); // Here is where we convert to `I`. + /// Appends index `i` to dimension `d`, in the semantically general + /// sense. For non-dense dimensions, that means appending to the + /// `indices[d]` array, checking that `i` is representable in the `I` + /// type; however, we do not verify other semantic requirements (e.g., + /// that `i` is in bounds for `sizes[d]`, and not previously occurring + /// in the same segment). For dense dimensions, this method instead + /// appends the appropriate number of zeros to the `values` array, + /// where `full` is the number of "entries" already written to `values` + /// for this segment. + /// + /// Returns: the new value of `full` for this segment. + uint64_t appendIndex(uint64_t d, uint64_t full, uint64_t i) { + if (isCompressedDim(d)) { + assert(i <= std::numeric_limits::max() && + "Index value is too large for the I-type"); + indices[d].push_back(static_cast(i)); + // We wrote nothing to `values`, so `full` remains the same. + return full; + } + // else dense dimension: + assert(i >= full && "Index was already filled"); + if (i > full) { // Short-circuit the `i==full` case, since it'll be a nop. + if (d + 1 == getRank()) + values.insert(values.end(), i - full, 0); + else + finalizeSegment(d + 1, 0, i - full); + } + // We've now written entries for all indices [0..i], and that set + // has cardinality `i+1`. + return i + 1; } /// Initializes sparse tensor storage scheme from a memory-resident sparse @@ -457,29 +490,13 @@ while (seg < hi && elements[seg].indices[d] == i) seg++; // Handle segment in interval for sparse or dense dimension. - if (isCompressedDim(d)) { - appendIndex(d, i); - } else { - // For dense storage we must fill in all the zero values between - // the previous element (when last we ran this for-loop) and the - // current element. - for (; full < i; full++) - endDim(d + 1); - full++; - } + full = appendIndex(d, full, i); fromCOO(elements, lo, seg, d + 1); // And move on to next segment in interval. lo = seg; } // Finalize the sparse pointer structure at this dimension. - if (isCompressedDim(d)) { - appendPointer(d); - } else { - // For dense storage we must fill in all the zero values after - // the last element. - for (uint64_t sz = sizes[d]; full < sz; full++) - endDim(d + 1); - } + finalizeSegment(d, full); } /// Stores the sparse tensor storage scheme into a memory-resident sparse @@ -505,16 +522,26 @@ } } - /// Ends a deeper, never seen before dimension. - void endDim(uint64_t d) { - assert(d <= getRank()); - if (d == getRank()) { - values.push_back(0); - } else if (isCompressedDim(d)) { - appendPointer(d); - } else { - for (uint64_t full = 0, sz = sizes[d]; full < sz; full++) - endDim(d + 1); + /// Finalize the sparse pointer structure at this dimension. + void finalizeSegment(uint64_t d, uint64_t full = 0, uint64_t count = 1) { + if (count == 0) + return; // Short-circuit, since it'll be a nop. + if (isCompressedDim(d)) { + appendPointer(d, indices[d].size(), count); + } else { // Dense dimension. + const uint64_t sz = sizes[d]; + assert(sz >= full && "Segment is overfull"); + // Assuming we check for overflows in the constructor, then this + // multiply will never overflow. + count *= sz - full; + // For dense storage we must enumerate all the remaining coordinates + // in this dimension (i.e., coordinates after the last non-zero + // element), and either fill in their zero values or else recurse + // to finalize some deeper dimension. + if (d + 1 == getRank()) + values.insert(values.end(), count, 0); + else + finalizeSegment(d + 1, 0, count); } } @@ -523,13 +550,8 @@ uint64_t rank = getRank(); assert(diff <= rank); for (uint64_t i = 0; i < rank - diff; i++) { - uint64_t d = rank - i - 1; - if (isCompressedDim(d)) { - appendPointer(d); - } else { - for (uint64_t full = idx[d] + 1, sz = sizes[d]; full < sz; full++) - endDim(d + 1); - } + const uint64_t d = rank - i - 1; + finalizeSegment(d, idx[d] + 1); } } @@ -539,12 +561,7 @@ assert(diff < rank); for (uint64_t d = diff; d < rank; d++) { uint64_t i = cursor[d]; - if (isCompressedDim(d)) { - appendIndex(d, i); - } else { - for (uint64_t full = top; full < i; full++) - endDim(d + 1); - } + appendIndex(d, top, i); top = 0; idx[d] = i; }