1 //===-- xray_segmented_array.h ---------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 // This file is a part of XRay, a dynamic runtime instrumentation system.
11 // Defines the implementation of a segmented array, with fixed-size segments
12 // backing the segments.
14 //===----------------------------------------------------------------------===//
15 #ifndef XRAY_SEGMENTED_ARRAY_H
16 #define XRAY_SEGMENTED_ARRAY_H
18 #include "sanitizer_common/sanitizer_allocator.h"
19 #include "xray_allocator.h"
20 #include "xray_utils.h"
22 #include <type_traits>
27 /// The Array type provides an interface similar to std::vector<...> but does
28 /// not shrink in size. Once constructed, elements can be appended but cannot be
29 /// removed. The implementation is heavily dependent on the contract provided by
30 /// the Allocator type, in that all memory will be released when the Allocator
31 /// is destroyed. When an Array is destroyed, it will destroy elements in the
32 /// backing store but will not free the memory.
33 template <class T
> class Array
{
41 // Each segment of the array will be laid out with the following assumptions:
43 // - Each segment will be on a cache-line address boundary (kCacheLineSize
46 // - The elements will be accessed through an aligned pointer, dependent on
47 // the alignment of T.
49 // - Each element is at least two-pointers worth from the beginning of the
50 // Segment, aligned properly, and the rest of the elements are accessed
51 // through appropriate alignment.
53 // We then compute the size of the segment to follow this logic:
55 // - Compute the number of elements that can fit within
56 // kCacheLineSize-multiple segments, minus the size of two pointers.
58 // - Request cacheline-multiple sized elements from the allocator.
59 static constexpr uint64_t AlignedElementStorageSize
= sizeof(T
);
61 static constexpr uint64_t SegmentControlBlockSize
= sizeof(Segment
*) * 2;
63 static constexpr uint64_t SegmentSize
= nearest_boundary(
64 SegmentControlBlockSize
+ next_pow2(sizeof(T
)), kCacheLineSize
);
66 using AllocatorType
= Allocator
<SegmentSize
>;
68 static constexpr uint64_t ElementsPerSegment
=
69 (SegmentSize
- SegmentControlBlockSize
) / next_pow2(sizeof(T
));
71 static_assert(ElementsPerSegment
> 0,
72 "Must have at least 1 element per segment.");
74 static Segment SentinelSegment
;
76 using size_type
= uint64_t;
79 // This Iterator models a BidirectionalIterator.
80 template <class U
> class Iterator
{
81 Segment
*S
= &SentinelSegment
;
86 Iterator(Segment
*IS
, uint64_t Off
, uint64_t S
) XRAY_NEVER_INSTRUMENT
90 Iterator(const Iterator
&) NOEXCEPT XRAY_NEVER_INSTRUMENT
= default;
91 Iterator() NOEXCEPT XRAY_NEVER_INSTRUMENT
= default;
92 Iterator(Iterator
&&) NOEXCEPT XRAY_NEVER_INSTRUMENT
= default;
93 Iterator
&operator=(const Iterator
&) XRAY_NEVER_INSTRUMENT
= default;
94 Iterator
&operator=(Iterator
&&) XRAY_NEVER_INSTRUMENT
= default;
95 ~Iterator() XRAY_NEVER_INSTRUMENT
= default;
97 Iterator
&operator++() XRAY_NEVER_INSTRUMENT
{
98 if (++Offset
% ElementsPerSegment
|| Offset
== Size
)
101 // At this point, we know that Offset % N == 0, so we must advance the
103 DCHECK_EQ(Offset
% ElementsPerSegment
, 0);
104 DCHECK_NE(Offset
, Size
);
105 DCHECK_NE(S
, &SentinelSegment
);
106 DCHECK_NE(S
->Next
, &SentinelSegment
);
108 DCHECK_NE(S
, &SentinelSegment
);
112 Iterator
&operator--() XRAY_NEVER_INSTRUMENT
{
113 DCHECK_NE(S
, &SentinelSegment
);
114 DCHECK_GT(Offset
, 0);
116 auto PreviousOffset
= Offset
--;
117 if (PreviousOffset
!= Size
&& PreviousOffset
% ElementsPerSegment
== 0) {
118 DCHECK_NE(S
->Prev
, &SentinelSegment
);
125 Iterator
operator++(int) XRAY_NEVER_INSTRUMENT
{
126 Iterator
Copy(*this);
131 Iterator
operator--(int) XRAY_NEVER_INSTRUMENT
{
132 Iterator
Copy(*this);
137 template <class V
, class W
>
138 friend bool operator==(const Iterator
<V
> &L
,
139 const Iterator
<W
> &R
) XRAY_NEVER_INSTRUMENT
{
140 return L
.S
== R
.S
&& L
.Offset
== R
.Offset
;
143 template <class V
, class W
>
144 friend bool operator!=(const Iterator
<V
> &L
,
145 const Iterator
<W
> &R
) XRAY_NEVER_INSTRUMENT
{
149 U
&operator*() const XRAY_NEVER_INSTRUMENT
{
150 DCHECK_NE(S
, &SentinelSegment
);
151 auto RelOff
= Offset
% ElementsPerSegment
;
153 // We need to compute the character-aligned pointer, offset from the
154 // segment's Data location to get the element in the position of Offset.
155 auto Base
= &S
->Data
;
156 auto AlignedOffset
= Base
+ (RelOff
* AlignedElementStorageSize
);
157 return *reinterpret_cast<U
*>(AlignedOffset
);
160 U
*operator->() const XRAY_NEVER_INSTRUMENT
{ return &(**this); }
163 AllocatorType
*Alloc
;
167 // Here we keep track of segments in the freelist, to allow us to re-use
168 // segments when elements are trimmed off the end.
172 // ===============================
173 // In the following implementation, we work through the algorithms and the
174 // list operations using the following notation:
176 // - pred(s) is the predecessor (previous node accessor) and succ(s) is
177 // the successor (next node accessor).
179 // - S is a sentinel segment, which has the following property:
181 // pred(S) == succ(S) == S
183 // - @ is a loop operator, which can imply pred(s) == s if it appears on
184 // the left of s, or succ(s) == S if it appears on the right of s.
186 // - sL <-> sR : means a bidirectional relation between sL and sR, which
189 // succ(sL) == sR && pred(SR) == sL
191 // - sL -> sR : implies a unidirectional relation between sL and SR,
192 // with the following properties:
196 // sL <- sR : implies a unidirectional relation between sR and sL,
197 // with the following properties:
201 // ===============================
203 Segment
*NewSegment() XRAY_NEVER_INSTRUMENT
{
204 // We need to handle the case in which enough elements have been trimmed to
205 // allow us to re-use segments we've allocated before. For this we look into
206 // the Freelist, to see whether we need to actually allocate new blocks or
207 // just re-use blocks we've already seen before.
208 if (Freelist
!= &SentinelSegment
) {
209 // The current state of lists resemble something like this at this point:
211 // Freelist: @S@<-f0->...<->fN->@S@
214 // We want to perform a splice of `f0` from Freelist to a temporary list,
217 // Templist: @S@<-f0->@S@
220 // Our algorithm preconditions are:
221 DCHECK_EQ(Freelist
->Prev
, &SentinelSegment
);
223 // Then the algorithm we implement is:
226 // Freelist = succ(Freelist)
227 // if (Freelist != S)
228 // pred(Freelist) = S
232 auto *FreeSegment
= Freelist
;
233 Freelist
= Freelist
->Next
;
235 // Note that we need to handle the case where Freelist is now pointing to
236 // S, which we don't want to be overwriting.
237 // TODO: Determine whether the cost of the branch is higher than the cost
238 // of the blind assignment.
239 if (Freelist
!= &SentinelSegment
)
240 Freelist
->Prev
= &SentinelSegment
;
242 FreeSegment
->Next
= &SentinelSegment
;
243 FreeSegment
->Prev
= &SentinelSegment
;
245 // Our postconditions are:
246 DCHECK_EQ(Freelist
->Prev
, &SentinelSegment
);
247 DCHECK_NE(FreeSegment
, &SentinelSegment
);
251 auto SegmentBlock
= Alloc
->Allocate();
252 if (SegmentBlock
.Data
== nullptr)
255 // Placement-new the Segment element at the beginning of the SegmentBlock.
256 new (SegmentBlock
.Data
) Segment
{&SentinelSegment
, &SentinelSegment
, {0}};
257 auto SB
= reinterpret_cast<Segment
*>(SegmentBlock
.Data
);
261 Segment
*InitHeadAndTail() XRAY_NEVER_INSTRUMENT
{
262 DCHECK_EQ(Head
, &SentinelSegment
);
263 DCHECK_EQ(Tail
, &SentinelSegment
);
264 auto S
= NewSegment();
267 DCHECK_EQ(S
->Next
, &SentinelSegment
);
268 DCHECK_EQ(S
->Prev
, &SentinelSegment
);
269 DCHECK_NE(S
, &SentinelSegment
);
272 DCHECK_EQ(Head
, Tail
);
273 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
274 DCHECK_EQ(Tail
->Prev
, &SentinelSegment
);
278 Segment
*AppendNewSegment() XRAY_NEVER_INSTRUMENT
{
279 auto S
= NewSegment();
282 DCHECK_NE(Tail
, &SentinelSegment
);
283 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
284 DCHECK_EQ(S
->Prev
, &SentinelSegment
);
285 DCHECK_EQ(S
->Next
, &SentinelSegment
);
289 DCHECK_EQ(S
, S
->Prev
->Next
);
290 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
295 explicit Array(AllocatorType
&A
) XRAY_NEVER_INSTRUMENT
297 Head(&SentinelSegment
),
298 Tail(&SentinelSegment
),
299 Freelist(&SentinelSegment
),
302 Array() XRAY_NEVER_INSTRUMENT
: Alloc(nullptr),
303 Head(&SentinelSegment
),
304 Tail(&SentinelSegment
),
305 Freelist(&SentinelSegment
),
308 Array(const Array
&) = delete;
309 Array
&operator=(const Array
&) = delete;
311 Array(Array
&&O
) XRAY_NEVER_INSTRUMENT
: Alloc(O
.Alloc
),
314 Freelist(O
.Freelist
),
317 O
.Head
= &SentinelSegment
;
318 O
.Tail
= &SentinelSegment
;
320 O
.Freelist
= &SentinelSegment
;
323 Array
&operator=(Array
&&O
) XRAY_NEVER_INSTRUMENT
{
327 O
.Head
= &SentinelSegment
;
329 O
.Tail
= &SentinelSegment
;
330 Freelist
= O
.Freelist
;
331 O
.Freelist
= &SentinelSegment
;
337 ~Array() XRAY_NEVER_INSTRUMENT
{
338 for (auto &E
: *this)
342 bool empty() const XRAY_NEVER_INSTRUMENT
{ return Size
== 0; }
344 AllocatorType
&allocator() const XRAY_NEVER_INSTRUMENT
{
345 DCHECK_NE(Alloc
, nullptr);
349 uint64_t size() const XRAY_NEVER_INSTRUMENT
{ return Size
; }
351 template <class... Args
>
352 T
*AppendEmplace(Args
&&... args
) XRAY_NEVER_INSTRUMENT
{
353 DCHECK((Size
== 0 && Head
== &SentinelSegment
&& Head
== Tail
) ||
354 (Size
!= 0 && Head
!= &SentinelSegment
&& Tail
!= &SentinelSegment
));
355 if (UNLIKELY(Head
== &SentinelSegment
)) {
356 auto R
= InitHeadAndTail();
361 DCHECK_NE(Head
, &SentinelSegment
);
362 DCHECK_NE(Tail
, &SentinelSegment
);
364 auto Offset
= Size
% ElementsPerSegment
;
365 if (UNLIKELY(Size
!= 0 && Offset
== 0))
366 if (AppendNewSegment() == nullptr)
369 DCHECK_NE(Tail
, &SentinelSegment
);
370 auto Base
= &Tail
->Data
;
371 auto AlignedOffset
= Base
+ (Offset
* AlignedElementStorageSize
);
372 DCHECK_LE(AlignedOffset
+ sizeof(T
),
373 reinterpret_cast<unsigned char *>(Base
) + SegmentSize
);
375 // In-place construct at Position.
376 new (AlignedOffset
) T
{std::forward
<Args
>(args
)...};
378 return reinterpret_cast<T
*>(AlignedOffset
);
381 T
*Append(const T
&E
) XRAY_NEVER_INSTRUMENT
{
382 // FIXME: This is a duplication of AppenEmplace with the copy semantics
383 // explicitly used, as a work-around to GCC 4.8 not invoking the copy
384 // constructor with the placement new with braced-init syntax.
385 DCHECK((Size
== 0 && Head
== &SentinelSegment
&& Head
== Tail
) ||
386 (Size
!= 0 && Head
!= &SentinelSegment
&& Tail
!= &SentinelSegment
));
387 if (UNLIKELY(Head
== &SentinelSegment
)) {
388 auto R
= InitHeadAndTail();
393 DCHECK_NE(Head
, &SentinelSegment
);
394 DCHECK_NE(Tail
, &SentinelSegment
);
396 auto Offset
= Size
% ElementsPerSegment
;
397 if (UNLIKELY(Size
!= 0 && Offset
== 0))
398 if (AppendNewSegment() == nullptr)
401 DCHECK_NE(Tail
, &SentinelSegment
);
402 auto Base
= &Tail
->Data
;
403 auto AlignedOffset
= Base
+ (Offset
* AlignedElementStorageSize
);
404 DCHECK_LE(AlignedOffset
+ sizeof(T
),
405 reinterpret_cast<unsigned char *>(Tail
) + SegmentSize
);
407 // In-place construct at Position.
408 new (AlignedOffset
) T(E
);
410 return reinterpret_cast<T
*>(AlignedOffset
);
413 T
&operator[](uint64_t Offset
) const XRAY_NEVER_INSTRUMENT
{
414 DCHECK_LE(Offset
, Size
);
415 // We need to traverse the array enough times to find the element at Offset.
417 while (Offset
>= ElementsPerSegment
) {
419 Offset
-= ElementsPerSegment
;
420 DCHECK_NE(S
, &SentinelSegment
);
422 auto Base
= &S
->Data
;
423 auto AlignedOffset
= Base
+ (Offset
* AlignedElementStorageSize
);
424 auto Position
= reinterpret_cast<T
*>(AlignedOffset
);
425 return *reinterpret_cast<T
*>(Position
);
428 T
&front() const XRAY_NEVER_INSTRUMENT
{
429 DCHECK_NE(Head
, &SentinelSegment
);
434 T
&back() const XRAY_NEVER_INSTRUMENT
{
435 DCHECK_NE(Tail
, &SentinelSegment
);
442 template <class Predicate
>
443 T
*find_element(Predicate P
) const XRAY_NEVER_INSTRUMENT
{
448 for (auto I
= begin(); I
!= E
; ++I
)
455 /// Remove N Elements from the end. This leaves the blocks behind, and not
456 /// require allocation of new blocks for new elements added after trimming.
457 void trim(uint64_t Elements
) XRAY_NEVER_INSTRUMENT
{
459 Elements
= Elements
> Size
? Size
: Elements
;
462 // We compute the number of segments we're going to return from the tail by
463 // counting how many elements have been trimmed. Given the following:
465 // - Each segment has N valid positions, where N > 0
466 // - The previous size > current size
468 // To compute the number of segments to return, we need to perform the
469 // following calculations for the number of segments required given 'x'
475 // , N < x <= max : x / N + (x % N ? 1 : 0)
478 // We can simplify this down to:
482 // , 0 < x <= max : x / N + (x < N || x % N ? 1 : 0)
485 // And further down to:
487 // f(x) = x ? x / N + (x < N || x % N ? 1 : 0) : 0
489 // We can then perform the following calculation `s` which counts the number
490 // of segments we need to remove from the end of the data structure:
492 // s(p, c) = f(p) - f(c)
494 // If we treat p = previous size, and c = current size, and given the
495 // properties above, the possible range for s(...) is [0..max(typeof(p))/N]
496 // given that typeof(p) == typeof(c).
497 auto F
= [](uint64_t X
) {
498 return X
? (X
/ ElementsPerSegment
) +
499 (X
< ElementsPerSegment
|| X
% ElementsPerSegment
? 1 : 0)
502 auto PS
= F(OldSize
);
505 auto SegmentsToTrim
= PS
- CS
;
506 for (auto I
= 0uL; I
< SegmentsToTrim
; ++I
) {
507 // Here we place the current tail segment to the freelist. To do this
508 // appropriately, we need to perform a splice operation on two
509 // bidirectional linked-lists. In particular, we have the current state of
510 // the doubly-linked list of segments:
512 // @S@ <- s0 <-> s1 <-> ... <-> sT -> @S@
514 DCHECK_NE(Head
, &SentinelSegment
);
515 DCHECK_NE(Tail
, &SentinelSegment
);
516 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
518 if (Freelist
== &SentinelSegment
) {
519 // Our two lists at this point are in this configuration:
521 // Freelist: (potentially) @S@
522 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@
525 // The end state for us will be this configuration:
527 // Freelist: @S@<-sT->@S@
528 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@
531 // The first step for us is to hold a reference to the tail of Mainlist,
532 // which in our notation is represented by sT. We call this our "free
533 // segment" which is the segment we are placing on the Freelist.
537 // Then, we also hold a reference to the "pre-tail" element, which we
542 // We want to splice sT into the beginning of the Freelist, which in
543 // an empty Freelist means placing a segment whose predecessor and
544 // successor is the sentinel segment.
546 // The splice operation then can be performed in the following
551 // succ(sT) = Freelist
555 auto SPT
= Tail
->Prev
;
556 SPT
->Next
= &SentinelSegment
;
557 Tail
->Prev
= &SentinelSegment
;
558 Tail
->Next
= Freelist
;
562 // Our post-conditions here are:
563 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
564 DCHECK_EQ(Freelist
->Prev
, &SentinelSegment
);
566 // In the other case, where the Freelist is not empty, we perform the
567 // following transformation instead:
569 // This transforms the current state:
571 // Freelist: @S@<-f0->@S@
573 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@
576 // Into the following:
578 // Freelist: @S@<-sT<->f0->@S@
580 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@
588 // succ(sT) = Freelist
595 auto SPT
= Tail
->Prev
;
599 ST
->Prev
= &SentinelSegment
;
600 SPT
->Next
= &SentinelSegment
;
604 // Our post-conditions here are:
605 DCHECK_EQ(Tail
->Next
, &SentinelSegment
);
606 DCHECK_EQ(Freelist
->Prev
, &SentinelSegment
);
607 DCHECK_EQ(Freelist
->Next
->Prev
, Freelist
);
611 // Now in case we've spliced all the segments in the end, we ensure that the
612 // main list is "empty", or both the head and tail pointing to the sentinel
614 if (Tail
== &SentinelSegment
)
618 (Size
== 0 && Head
== &SentinelSegment
&& Tail
== &SentinelSegment
) ||
619 (Size
!= 0 && Head
!= &SentinelSegment
&& Tail
!= &SentinelSegment
));
621 (Freelist
!= &SentinelSegment
&& Freelist
->Prev
== &SentinelSegment
) ||
622 (Freelist
== &SentinelSegment
&& Tail
->Next
== &SentinelSegment
));
625 // Provide iterators.
626 Iterator
<T
> begin() const XRAY_NEVER_INSTRUMENT
{
627 return Iterator
<T
>(Head
, 0, Size
);
629 Iterator
<T
> end() const XRAY_NEVER_INSTRUMENT
{
630 return Iterator
<T
>(Tail
, Size
, Size
);
632 Iterator
<const T
> cbegin() const XRAY_NEVER_INSTRUMENT
{
633 return Iterator
<const T
>(Head
, 0, Size
);
635 Iterator
<const T
> cend() const XRAY_NEVER_INSTRUMENT
{
636 return Iterator
<const T
>(Tail
, Size
, Size
);
640 // We need to have this storage definition out-of-line so that the compiler can
641 // ensure that storage for the SentinelSegment is defined and has a single
644 typename Array
<T
>::Segment Array
<T
>::SentinelSegment
{
645 &Array
<T
>::SentinelSegment
, &Array
<T
>::SentinelSegment
, {'\0'}};
647 } // namespace __xray
649 #endif // XRAY_SEGMENTED_ARRAY_H