Skip to content

Commit ef1851a

Browse files
committed
ADT/Matrix: two-dimensional Container with View
Leverage the excellent SmallVector infrastructure to write a two-dimensional container, along with a View that abstracts out indexing-arithmetic, eliminating memory operations on the underlying storage. The immediate applicability of Matrix is to replace uses of the vector-of-vectors idiom, with one caveat: an upper bound on the number of columns should be known ahead of time.
1 parent c5ee3c0 commit ef1851a

File tree

4 files changed

+668
-0
lines changed

4 files changed

+668
-0
lines changed

llvm/include/llvm/ADT/ArrayRef.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
namespace llvm {
2727
template<typename T> class [[nodiscard]] MutableArrayRef;
28+
template <typename T> struct [[nodiscard]] MutableRowView;
2829

2930
/// ArrayRef - Represent a constant reference to an array (0 or more elements
3031
/// consecutively in memory), i.e. a start pointer and a length. It allows
@@ -59,6 +60,8 @@ namespace llvm {
5960
/// The number of elements.
6061
size_type Length = 0;
6162

63+
friend MutableRowView<T>;
64+
6265
public:
6366
/// @name Constructors
6467
/// @{

llvm/include/llvm/ADT/Matrix.h

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
//===- Matrix.h - Two-dimensional Container with View -----------*- C++ -*-===//
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-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_ADT_MATRIX_H
10+
#define LLVM_ADT_MATRIX_H
11+
12+
#include "llvm/ADT/ArrayRef.h"
13+
#include "llvm/ADT/SmallVector.h"
14+
15+
namespace llvm {
16+
template <typename T, size_t M, size_t NStorageInline> class JaggedArrayView;
17+
18+
/// Due to the SmallVector infrastructure using SmallVectorAlignmentAndOffset
19+
/// that depends on the exact data layout, no derived classes can have extra
20+
/// members.
21+
template <typename T, size_t N>
22+
struct MatrixStorageBase : public SmallVectorImpl<T>, SmallVectorStorage<T, N> {
23+
MatrixStorageBase() : SmallVectorImpl<T>(N) {}
24+
MatrixStorageBase(size_t Size) : SmallVectorImpl<T>(N) { resize(Size); }
25+
~MatrixStorageBase() { destroy_range(this->begin(), this->end()); }
26+
MatrixStorageBase(const MatrixStorageBase &RHS) : SmallVectorImpl<T>(N) {
27+
if (!RHS.empty())
28+
SmallVectorImpl<T>::operator=(RHS);
29+
}
30+
MatrixStorageBase(MatrixStorageBase &&RHS) : SmallVectorImpl<T>(N) {
31+
if (!RHS.empty())
32+
SmallVectorImpl<T>::operator=(::std::move(RHS));
33+
}
34+
using SmallVectorImpl<T>::size;
35+
using SmallVectorImpl<T>::resize;
36+
using SmallVectorImpl<T>::append;
37+
using SmallVectorImpl<T>::erase;
38+
using SmallVectorImpl<T>::destroy_range;
39+
40+
T *begin() const { return const_cast<T *>(SmallVectorImpl<T>::begin()); }
41+
T *end() const { return const_cast<T *>(SmallVectorImpl<T>::end()); }
42+
};
43+
44+
/// A two-dimensional container storage, whose upper bound on the number of
45+
/// columns should be known ahead of time. Not menat to be used directly: the
46+
/// primary usage API is MatrixView.
47+
template <typename T,
48+
size_t N = CalculateSmallVectorDefaultInlinedElements<T>::value>
49+
class MatrixStorage {
50+
public:
51+
MatrixStorage() = delete;
52+
MatrixStorage(size_t NRows, size_t NCols)
53+
: Base(NRows * NCols), NCols(NCols) {}
54+
MatrixStorage(size_t NCols) : Base(), NCols(NCols) {}
55+
56+
size_t size() const { return Base.size(); }
57+
bool empty() const { return !size(); }
58+
size_t getNumRows() const { return size() / NCols; }
59+
size_t getNumCols() const { return NCols; }
60+
void setNumCols(size_t NCols) {
61+
assert(empty() && "Column-resizing a non-empty MatrixStorage");
62+
this->NCols = NCols;
63+
}
64+
void resize(size_t NRows) { Base.resize(NCols * NRows); }
65+
66+
protected:
67+
template <typename U, size_t M, size_t NStorageInline>
68+
friend class JaggedArrayView;
69+
70+
T *begin() const { return Base.begin(); }
71+
T *rowFromIdx(size_t RowIdx, size_t Offset = 0) const {
72+
return begin() + RowIdx * NCols + Offset;
73+
}
74+
std::pair<size_t, size_t> idxFromRow(T *Ptr) const {
75+
assert(Ptr >= begin() && "Internal error");
76+
size_t Offset = (Ptr - begin()) % NCols;
77+
return {(Ptr - begin()) / NCols, Offset};
78+
}
79+
80+
// If Arg.size() < NCols, the number of columns won't be changed, and the
81+
// difference is default-constructed.
82+
void addRow(const SmallVectorImpl<T> &Arg) {
83+
assert(Arg.size() <= NCols &&
84+
"MatrixStorage has insufficient number of columns");
85+
size_t Diff = NCols - Arg.size();
86+
Base.append(Arg.begin(), Arg.end());
87+
Base.append(Diff, T());
88+
}
89+
90+
private:
91+
MatrixStorageBase<T, N> Base;
92+
size_t NCols;
93+
};
94+
95+
/// MutableArrayRef with a copy-assign, and extra APIs.
96+
template <typename T>
97+
struct [[nodiscard]] MutableRowView : public MutableArrayRef<T> {
98+
using pointer = typename MutableArrayRef<T>::pointer;
99+
using iterator = typename MutableArrayRef<T>::iterator;
100+
using const_iterator = typename MutableArrayRef<T>::const_iterator;
101+
102+
MutableRowView() = delete;
103+
MutableRowView(pointer Data, size_t Length)
104+
: MutableArrayRef<T>(Data, Length) {}
105+
MutableRowView(iterator Begin, iterator End)
106+
: MutableArrayRef<T>(Begin, End) {}
107+
MutableRowView(const_iterator Begin, const_iterator End)
108+
: MutableArrayRef<T>(Begin, End) {}
109+
MutableRowView(MutableArrayRef<T> Other)
110+
: MutableArrayRef<T>(Other.data(), Other.size()) {}
111+
MutableRowView(const SmallVectorImpl<T> &Vec) : MutableArrayRef<T>(Vec) {}
112+
113+
using MutableArrayRef<T>::size;
114+
using MutableArrayRef<T>::data;
115+
116+
T &back() const { return MutableArrayRef<T>::back(); }
117+
T &front() const { return MutableArrayRef<T>::front(); }
118+
MutableRowView<T> drop_back(size_t N = 1) const { // NOLINT
119+
return MutableArrayRef<T>::drop_back(N);
120+
}
121+
MutableRowView<T> drop_front(size_t N = 1) const { // NOLINT
122+
return MutableArrayRef<T>::drop_front(N);
123+
}
124+
// This slice is different from the MutableArrayRef slice, and specifies a
125+
// Begin and End index, instead of a Begin and Length.
126+
MutableRowView<T> slice(size_t Begin, size_t End) {
127+
return MutableArrayRef<T>::slice(Begin, End - Begin);
128+
}
129+
void pop_back(size_t N = 1) { // NOLINT
130+
this->Length -= N;
131+
}
132+
void pop_front(size_t N = 1) { // NOLINT
133+
this->Data += N;
134+
this->Length -= N;
135+
}
136+
137+
MutableRowView &operator=(const SmallVectorImpl<T> &Vec) {
138+
copy_assign(Vec.begin(), Vec.end());
139+
return *this;
140+
}
141+
MutableRowView &operator=(std::initializer_list<T> IL) {
142+
copy_assign(IL.begin(), IL.end());
143+
return *this;
144+
}
145+
146+
void swap(MutableRowView<T> &Other) {
147+
std::swap(this->Data, Other.Data);
148+
std::swap(this->Length, Other.Length);
149+
}
150+
151+
protected:
152+
void copy_assign(iterator Begin, iterator End) { // NOLINT
153+
std::uninitialized_copy(Begin, End, data());
154+
this->Length = End - Begin;
155+
}
156+
void copy_assign(const_iterator Begin, const_iterator End) { // NOLINT
157+
std::uninitialized_copy(Begin, End, data());
158+
this->Length = End - Begin;
159+
}
160+
};
161+
162+
/// The primary usage API of MatrixStorage. Abstracts out indexing-arithmetic,
163+
/// eliminating memory operations on the underlying data. Supports
164+
/// variable-length columns.
165+
template <typename T,
166+
size_t N = CalculateSmallVectorDefaultInlinedElements<T>::value,
167+
size_t NStorageInline =
168+
CalculateSmallVectorDefaultInlinedElements<T>::value>
169+
class [[nodiscard]] JaggedArrayView {
170+
public:
171+
using row_type = MutableRowView<T>;
172+
using container_type = SmallVector<row_type, N>;
173+
using iterator = typename container_type::iterator;
174+
using const_iterator = typename container_type::const_iterator;
175+
176+
constexpr JaggedArrayView(MatrixStorage<T, NStorageInline> &Mat,
177+
size_t RowSpan, size_t ColSpan)
178+
: Mat(Mat) {
179+
RowView.reserve(RowSpan);
180+
for (size_t RowIdx = 0; RowIdx < RowSpan; ++RowIdx) {
181+
auto RangeBegin = Mat.begin() + RowIdx * ColSpan;
182+
RowView.emplace_back(RangeBegin, RangeBegin + ColSpan);
183+
}
184+
}
185+
186+
// Constructor with a full View of the underlying MatrixStorage, if
187+
// MatrixStorage has a non-zero number of Columns. Otherwise, creates an empty
188+
// view.
189+
constexpr JaggedArrayView(MatrixStorage<T, NStorageInline> &Mat)
190+
: JaggedArrayView(Mat, Mat.getNumRows(), Mat.getNumCols()) {}
191+
192+
// Obvious copy-construator is deleted, since the underlying storage could
193+
// have changed.
194+
constexpr JaggedArrayView(const JaggedArrayView &) = delete;
195+
196+
// Copy-assignment operator should not be used when the underlying storage
197+
// changes.
198+
constexpr JaggedArrayView &operator=(const JaggedArrayView &Other) {
199+
assert(Mat.begin() == Other.Mat.begin() &&
200+
"Underlying storage has changed: use custom copy-constructor");
201+
RowView = Other.RowView;
202+
return *this;
203+
}
204+
205+
// The actual copy-constructor: to be used when the underlying storage is
206+
// copy-constructed.
207+
JaggedArrayView(const JaggedArrayView &OldView,
208+
MatrixStorage<T, NStorageInline> &NewMat)
209+
: Mat(NewMat) {
210+
assert(OldView.Mat.size() == Mat.size() &&
211+
"Custom copy-constructor called on non-copied storage");
212+
213+
// The underlying storage will change. Construct a new RowView by performing
214+
// pointer-arithmetic on the underlying storage of OldView, using pointers
215+
// from OldVie.
216+
for (const auto &R : OldView.RowView) {
217+
auto [StorageIdx, StartOffset] = OldView.Mat.idxFromRow(R.data());
218+
RowView.emplace_back(Mat.rowFromIdx(StorageIdx, StartOffset), R.size());
219+
}
220+
}
221+
222+
void addRow(const SmallVectorImpl<T> &Row) {
223+
// The underlying storage may be resized, performing reallocations. The
224+
// pointers in RowView will no longer be valid, so save and restore the
225+
// data. Construct RestoreData by performing pointer-arithmetic on the
226+
// underlying storgge.
227+
SmallVector<std::tuple<size_t, size_t, size_t>> RestoreData;
228+
RestoreData.reserve(RowView.size());
229+
for (const auto &R : RowView) {
230+
auto [StorageIdx, StartOffset] = Mat.idxFromRow(R.data());
231+
RestoreData.emplace_back(StorageIdx, StartOffset, R.size());
232+
}
233+
234+
Mat.addRow(Row);
235+
236+
// Restore the RowView by performing pointer-arithmetic on the
237+
// possibly-reallocated storage, using information from RestoreData.
238+
RowView.clear();
239+
for (const auto &[StorageIdx, StartOffset, Len] : RestoreData)
240+
RowView.emplace_back(Mat.rowFromIdx(StorageIdx, StartOffset), Len);
241+
242+
// Finally, add the new row to the VRowView.
243+
RowView.emplace_back(Mat.rowFromIdx(Mat.getNumRows() - 1), Row.size());
244+
}
245+
246+
// To support addRow(View[Idx]).
247+
void addRow(const row_type &Row) { addRow(SmallVector<T>{Row}); }
248+
249+
void addRow(std::initializer_list<T> Row) { addRow(SmallVector<T>{Row}); }
250+
251+
constexpr row_type &operator[](size_t RowIdx) {
252+
assert(RowIdx < RowView.size() && "Indexing out of bounds");
253+
return RowView[RowIdx];
254+
}
255+
256+
constexpr T *data() const {
257+
assert(!empty() && "Non-empty view expected");
258+
return RowView.front().data();
259+
}
260+
size_t size() const { return getRowSpan() * getMaxColSpan(); }
261+
bool empty() const { return RowView.empty(); }
262+
size_t getRowSpan() const { return RowView.size(); }
263+
size_t getColSpan(size_t RowIdx) const {
264+
assert(RowIdx < RowView.size() && "Indexing out of bounds");
265+
return RowView[RowIdx].size();
266+
}
267+
constexpr size_t getMaxColSpan() const {
268+
return std::max_element(RowView.begin(), RowView.end(),
269+
[](const row_type &RowA, const row_type &RowB) {
270+
return RowA.size() < RowB.size();
271+
})
272+
->size();
273+
}
274+
275+
iterator begin() { return RowView.begin(); }
276+
iterator end() { return RowView.end(); }
277+
const_iterator begin() const { return RowView.begin(); }
278+
const_iterator end() const { return RowView.end(); }
279+
280+
constexpr JaggedArrayView<T, N, NStorageInline> rowSlice(size_t Begin,
281+
size_t End) {
282+
assert(Begin < getRowSpan() && End <= getRowSpan() &&
283+
"Indexing out of bounds");
284+
assert(Begin < End && "Invalid slice");
285+
container_type NewRowView;
286+
for (size_t RowIdx = Begin; RowIdx < End; ++RowIdx)
287+
NewRowView.emplace_back(RowView[RowIdx]);
288+
return {Mat, std::move(NewRowView)};
289+
}
290+
291+
constexpr JaggedArrayView<T, N, NStorageInline> colSlice(size_t Begin,
292+
size_t End) {
293+
assert(Begin < End && "Invalid slice");
294+
size_t MinColSpan =
295+
std::min_element(RowView.begin(), RowView.end(),
296+
[](const row_type &RowA, const row_type &RowB) {
297+
return RowA.size() < RowB.size();
298+
})
299+
->size();
300+
assert(Begin < MinColSpan && End <= MinColSpan && "Indexing out of bounds");
301+
container_type NewRowView;
302+
for (row_type Row : RowView)
303+
NewRowView.emplace_back(Row.slice(Begin, End));
304+
return {Mat, std::move(NewRowView)};
305+
}
306+
307+
row_type &lastRow() {
308+
assert(!empty() && "Non-empty view expected");
309+
return RowView.back();
310+
}
311+
const row_type &lastRow() const {
312+
assert(!empty() && "Non-empty view expected");
313+
return RowView.back();
314+
}
315+
void dropLastRow() {
316+
assert(!empty() && "Non-empty view expected");
317+
RowView.pop_back();
318+
}
319+
320+
protected:
321+
// Helper constructor.
322+
constexpr JaggedArrayView(MatrixStorage<T, NStorageInline> &Mat,
323+
SmallVectorImpl<row_type> &&RowView)
324+
: Mat(Mat), RowView(std::move(RowView)) {}
325+
326+
private:
327+
MatrixStorage<T, NStorageInline> &Mat;
328+
container_type RowView;
329+
};
330+
} // namespace llvm
331+
332+
namespace std {
333+
template <typename T>
334+
inline void swap(llvm::MutableRowView<T> &LHS, llvm::MutableRowView<T> &RHS) {
335+
LHS.swap(RHS);
336+
}
337+
} // end namespace std
338+
339+
#endif

llvm/unittests/ADT/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ add_llvm_unittest(ADTTests
5454
LazyAtomicPointerTest.cpp
5555
MappedIteratorTest.cpp
5656
MapVectorTest.cpp
57+
MatrixTest.cpp
5758
PackedVectorTest.cpp
5859
PagedVectorTest.cpp
5960
PointerEmbeddedIntTest.cpp

0 commit comments

Comments
 (0)