Skip to content

Commit 8ea8f1f

Browse files
authored
[scudo] Support linking with index in IntrusiveList (#101262)
The nodes of list may be managed in an array. Instead of managing them with pointers, using the array index will save some memory in some cases. When using the list linked with index, remember to call init() to set up the base address of the array and a `EndOfListVal` which is nil of the list.
1 parent 15aa4ef commit 8ea8f1f

File tree

2 files changed

+234
-70
lines changed

2 files changed

+234
-70
lines changed

compiler-rt/lib/scudo/standalone/list.h

Lines changed: 158 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,113 @@
1111

1212
#include "internal_defs.h"
1313

14+
// TODO: Move the helpers to a header.
15+
namespace {
16+
template <typename T> struct isPointer {
17+
static constexpr bool value = false;
18+
};
19+
20+
template <typename T> struct isPointer<T *> {
21+
static constexpr bool value = true;
22+
};
23+
} // namespace
24+
1425
namespace scudo {
1526

1627
// Intrusive POD singly and doubly linked list.
1728
// An object with all zero fields should represent a valid empty list. clear()
1829
// should be called on all non-zero-initialized objects before using.
30+
//
31+
// The intrusive list requires the member `Next` (and `Prev` if doubly linked
32+
// list)` defined in the node type. The type of `Next`/`Prev` can be a pointer
33+
// or an index to an array. For example, if the storage of the nodes is an
34+
// array, instead of using a pointer type, linking with an index type can save
35+
// some space.
36+
//
37+
// There are two things to be noticed while using an index type,
38+
// 1. Call init() to set up the base address of the array.
39+
// 2. Define `EndOfListVal` as the nil of the list.
40+
41+
template <class T, bool LinkWithPtr = isPointer<decltype(T::Next)>::value>
42+
class LinkOp {
43+
public:
44+
LinkOp() = default;
45+
LinkOp(UNUSED T *BaseT, UNUSED uptr BaseSize) {}
46+
void init(UNUSED T *LinkBase, UNUSED uptr Size) {}
47+
T *getBase() const { return nullptr; }
48+
uptr getSize() const { return 0; }
49+
50+
T *getNext(T *X) const { return X->Next; }
51+
void setNext(T *X, T *Next) const { X->Next = Next; }
52+
53+
T *getPrev(T *X) const { return X->Prev; }
54+
void setPrev(T *X, T *Prev) const { X->Prev = Prev; }
55+
56+
T *getEndOfListVal() const { return nullptr; }
57+
};
58+
59+
template <class T> class LinkOp<T, /*LinkWithPtr=*/false> {
60+
public:
61+
using LinkTy = decltype(T::Next);
62+
63+
LinkOp() = default;
64+
LinkOp(T *BaseT, uptr BaseSize) : Base(BaseT), Size(BaseSize) {}
65+
void init(T *LinkBase, uptr BaseSize) {
66+
Base = LinkBase;
67+
// TODO: Check if the `BaseSize` can fit in `Size`.
68+
Size = static_cast<LinkTy>(BaseSize);
69+
}
70+
T *getBase() const { return Base; }
71+
LinkTy getSize() const { return Size; }
72+
73+
T *getNext(T *X) const {
74+
DCHECK_NE(getBase(), nullptr);
75+
if (X->Next == getEndOfListVal())
76+
return nullptr;
77+
DCHECK_LT(X->Next, Size);
78+
return &Base[X->Next];
79+
}
80+
// Set `X->Next` to `Next`.
81+
void setNext(T *X, T *Next) const {
82+
// TODO: Check if the offset fits in the size of `LinkTy`.
83+
if (Next == nullptr)
84+
X->Next = getEndOfListVal();
85+
else
86+
X->Next = static_cast<LinkTy>(Next - Base);
87+
}
1988

20-
template <class T> class IteratorBase {
89+
T *getPrev(T *X) const {
90+
DCHECK_NE(getBase(), nullptr);
91+
if (X->Prev == getEndOfListVal())
92+
return nullptr;
93+
DCHECK_LT(X->Prev, Size);
94+
return &Base[X->Prev];
95+
}
96+
// Set `X->Prev` to `Prev`.
97+
void setPrev(T *X, T *Prev) const {
98+
DCHECK_LT(reinterpret_cast<uptr>(Prev),
99+
reinterpret_cast<uptr>(Base + Size));
100+
if (Prev == nullptr)
101+
X->Prev = getEndOfListVal();
102+
else
103+
X->Prev = static_cast<LinkTy>(Prev - Base);
104+
}
105+
106+
// TODO: `LinkTy` should be the same as decltype(T::EndOfListVal).
107+
LinkTy getEndOfListVal() const { return T::EndOfListVal; }
108+
109+
protected:
110+
T *Base = nullptr;
111+
LinkTy Size = 0;
112+
};
113+
114+
template <class T> class IteratorBase : public LinkOp<T> {
21115
public:
22-
explicit IteratorBase(T *CurrentT) : Current(CurrentT) {}
116+
IteratorBase(const LinkOp<T> &Link, T *CurrentT)
117+
: LinkOp<T>(Link), Current(CurrentT) {}
118+
23119
IteratorBase &operator++() {
24-
Current = Current->Next;
120+
Current = this->getNext(Current);
25121
return *this;
26122
}
27123
bool operator!=(IteratorBase Other) const { return Current != Other.Current; }
@@ -31,7 +127,10 @@ template <class T> class IteratorBase {
31127
T *Current;
32128
};
33129

34-
template <class T> struct IntrusiveList {
130+
template <class T> struct IntrusiveList : public LinkOp<T> {
131+
IntrusiveList() = default;
132+
void init(T *Base, uptr BaseSize) { LinkOp<T>::init(Base, BaseSize); }
133+
35134
bool empty() const { return Size == 0; }
36135
uptr size() const { return Size; }
37136

@@ -48,11 +147,21 @@ template <class T> struct IntrusiveList {
48147
typedef IteratorBase<T> Iterator;
49148
typedef IteratorBase<const T> ConstIterator;
50149

51-
Iterator begin() { return Iterator(First); }
52-
Iterator end() { return Iterator(nullptr); }
150+
Iterator begin() {
151+
return Iterator(LinkOp<T>(this->getBase(), this->getSize()), First);
152+
}
153+
Iterator end() {
154+
return Iterator(LinkOp<T>(this->getBase(), this->getSize()), nullptr);
155+
}
53156

54-
ConstIterator begin() const { return ConstIterator(First); }
55-
ConstIterator end() const { return ConstIterator(nullptr); }
157+
ConstIterator begin() const {
158+
return ConstIterator(LinkOp<const T>(this->getBase(), this->getSize()),
159+
First);
160+
}
161+
ConstIterator end() const {
162+
return ConstIterator(LinkOp<const T>(this->getBase(), this->getSize()),
163+
nullptr);
164+
}
56165

57166
void checkConsistency() const;
58167

@@ -68,13 +177,13 @@ template <class T> void IntrusiveList<T>::checkConsistency() const {
68177
CHECK_EQ(Last, nullptr);
69178
} else {
70179
uptr Count = 0;
71-
for (T *I = First;; I = I->Next) {
180+
for (T *I = First;; I = this->getNext(I)) {
72181
Count++;
73182
if (I == Last)
74183
break;
75184
}
76185
CHECK_EQ(this->size(), Count);
77-
CHECK_EQ(Last->Next, nullptr);
186+
CHECK_EQ(this->getNext(Last), nullptr);
78187
}
79188
}
80189

@@ -83,28 +192,31 @@ template <class T> struct SinglyLinkedList : public IntrusiveList<T> {
83192
using IntrusiveList<T>::Last;
84193
using IntrusiveList<T>::Size;
85194
using IntrusiveList<T>::empty;
195+
using IntrusiveList<T>::setNext;
196+
using IntrusiveList<T>::getNext;
197+
using IntrusiveList<T>::getEndOfListVal;
86198

87199
void push_back(T *X) {
88-
X->Next = nullptr;
200+
setNext(X, nullptr);
89201
if (empty())
90202
First = X;
91203
else
92-
Last->Next = X;
204+
setNext(Last, X);
93205
Last = X;
94206
Size++;
95207
}
96208

97209
void push_front(T *X) {
98210
if (empty())
99211
Last = X;
100-
X->Next = First;
212+
setNext(X, First);
101213
First = X;
102214
Size++;
103215
}
104216

105217
void pop_front() {
106218
DCHECK(!empty());
107-
First = First->Next;
219+
First = getNext(First);
108220
if (!First)
109221
Last = nullptr;
110222
Size--;
@@ -115,8 +227,8 @@ template <class T> struct SinglyLinkedList : public IntrusiveList<T> {
115227
DCHECK(!empty());
116228
DCHECK_NE(Prev, nullptr);
117229
DCHECK_NE(X, nullptr);
118-
X->Next = Prev->Next;
119-
Prev->Next = X;
230+
setNext(X, getNext(Prev));
231+
setNext(Prev, X);
120232
if (Last == Prev)
121233
Last = X;
122234
++Size;
@@ -126,8 +238,8 @@ template <class T> struct SinglyLinkedList : public IntrusiveList<T> {
126238
DCHECK(!empty());
127239
DCHECK_NE(Prev, nullptr);
128240
DCHECK_NE(X, nullptr);
129-
DCHECK_EQ(Prev->Next, X);
130-
Prev->Next = X->Next;
241+
DCHECK_EQ(getNext(Prev), X);
242+
setNext(Prev, getNext(X));
131243
if (Last == X)
132244
Last = Prev;
133245
Size--;
@@ -140,7 +252,7 @@ template <class T> struct SinglyLinkedList : public IntrusiveList<T> {
140252
if (empty()) {
141253
*this = *L;
142254
} else {
143-
Last->Next = L->First;
255+
setNext(Last, L->First);
144256
Last = L->Last;
145257
Size += L->size();
146258
}
@@ -153,16 +265,21 @@ template <class T> struct DoublyLinkedList : IntrusiveList<T> {
153265
using IntrusiveList<T>::Last;
154266
using IntrusiveList<T>::Size;
155267
using IntrusiveList<T>::empty;
268+
using IntrusiveList<T>::setNext;
269+
using IntrusiveList<T>::getNext;
270+
using IntrusiveList<T>::setPrev;
271+
using IntrusiveList<T>::getPrev;
272+
using IntrusiveList<T>::getEndOfListVal;
156273

157274
void push_front(T *X) {
158-
X->Prev = nullptr;
275+
setPrev(X, nullptr);
159276
if (empty()) {
160277
Last = X;
161278
} else {
162-
DCHECK_EQ(First->Prev, nullptr);
163-
First->Prev = X;
279+
DCHECK_EQ(getPrev(First), nullptr);
280+
setPrev(First, X);
164281
}
165-
X->Next = First;
282+
setNext(X, First);
166283
First = X;
167284
Size++;
168285
}
@@ -171,53 +288,53 @@ template <class T> struct DoublyLinkedList : IntrusiveList<T> {
171288
void insert(T *X, T *Y) {
172289
if (Y == First)
173290
return push_front(X);
174-
T *Prev = Y->Prev;
291+
T *Prev = getPrev(Y);
175292
// This is a hard CHECK to ensure consistency in the event of an intentional
176293
// corruption of Y->Prev, to prevent a potential write-{4,8}.
177-
CHECK_EQ(Prev->Next, Y);
178-
Prev->Next = X;
179-
X->Prev = Prev;
180-
X->Next = Y;
181-
Y->Prev = X;
294+
CHECK_EQ(getNext(Prev), Y);
295+
setNext(Prev, X);
296+
setPrev(X, Prev);
297+
setNext(X, Y);
298+
setPrev(Y, X);
182299
Size++;
183300
}
184301

185302
void push_back(T *X) {
186-
X->Next = nullptr;
303+
setNext(X, nullptr);
187304
if (empty()) {
188305
First = X;
189306
} else {
190-
DCHECK_EQ(Last->Next, nullptr);
191-
Last->Next = X;
307+
DCHECK_EQ(getNext(Last), nullptr);
308+
setNext(Last, X);
192309
}
193-
X->Prev = Last;
310+
setPrev(X, Last);
194311
Last = X;
195312
Size++;
196313
}
197314

198315
void pop_front() {
199316
DCHECK(!empty());
200-
First = First->Next;
317+
First = getNext(First);
201318
if (!First)
202319
Last = nullptr;
203320
else
204-
First->Prev = nullptr;
321+
setPrev(First, nullptr);
205322
Size--;
206323
}
207324

208325
// The consistency of the adjacent links is aggressively checked in order to
209326
// catch potential corruption attempts, that could yield a mirrored
210327
// write-{4,8} primitive. nullptr checks are deemed less vital.
211328
void remove(T *X) {
212-
T *Prev = X->Prev;
213-
T *Next = X->Next;
329+
T *Prev = getPrev(X);
330+
T *Next = getNext(X);
214331
if (Prev) {
215-
CHECK_EQ(Prev->Next, X);
216-
Prev->Next = Next;
332+
CHECK_EQ(getNext(Prev), X);
333+
setNext(Prev, Next);
217334
}
218335
if (Next) {
219-
CHECK_EQ(Next->Prev, X);
220-
Next->Prev = Prev;
336+
CHECK_EQ(getPrev(Next), X);
337+
setPrev(Next, Prev);
221338
}
222339
if (First == X) {
223340
DCHECK_EQ(Prev, nullptr);

0 commit comments

Comments
 (0)