llvm-project/compiler-rt/lib/xray/xray_segmented_array.h

651 lines
21 KiB
C++

//===-- xray_segmented_array.h ---------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is a part of XRay, a dynamic runtime instrumentation system.
//
// Defines the implementation of a segmented array, with fixed-size segments
// backing the segments.
//
//===----------------------------------------------------------------------===//
#ifndef XRAY_SEGMENTED_ARRAY_H
#define XRAY_SEGMENTED_ARRAY_H
#include "sanitizer_common/sanitizer_allocator.h"
#include "xray_allocator.h"
#include "xray_utils.h"
#include <cassert>
#include <type_traits>
#include <utility>
namespace __xray {
/// The Array type provides an interface similar to std::vector<...> but does
/// not shrink in size. Once constructed, elements can be appended but cannot be
/// removed. The implementation is heavily dependent on the contract provided by
/// the Allocator type, in that all memory will be released when the Allocator
/// is destroyed. When an Array is destroyed, it will destroy elements in the
/// backing store but will not free the memory.
template <class T> class Array {
struct Segment {
Segment *Prev;
Segment *Next;
char Data[1];
};
public:
// Each segment of the array will be laid out with the following assumptions:
//
// - Each segment will be on a cache-line address boundary (kCacheLineSize
// aligned).
//
// - The elements will be accessed through an aligned pointer, dependent on
// the alignment of T.
//
// - Each element is at least two-pointers worth from the beginning of the
// Segment, aligned properly, and the rest of the elements are accessed
// through appropriate alignment.
//
// We then compute the size of the segment to follow this logic:
//
// - Compute the number of elements that can fit within
// kCacheLineSize-multiple segments, minus the size of two pointers.
//
// - Request cacheline-multiple sized elements from the allocator.
static constexpr uint64_t AlignedElementStorageSize =
sizeof(typename std::aligned_storage<sizeof(T), alignof(T)>::type);
static constexpr uint64_t SegmentControlBlockSize = sizeof(Segment *) * 2;
static constexpr uint64_t SegmentSize = nearest_boundary(
SegmentControlBlockSize + next_pow2(sizeof(T)), kCacheLineSize);
using AllocatorType = Allocator<SegmentSize>;
static constexpr uint64_t ElementsPerSegment =
(SegmentSize - SegmentControlBlockSize) / next_pow2(sizeof(T));
static_assert(ElementsPerSegment > 0,
"Must have at least 1 element per segment.");
static Segment SentinelSegment;
using size_type = uint64_t;
private:
// This Iterator models a BidirectionalIterator.
template <class U> class Iterator {
Segment *S = &SentinelSegment;
uint64_t Offset = 0;
uint64_t Size = 0;
public:
Iterator(Segment *IS, uint64_t Off, uint64_t S) XRAY_NEVER_INSTRUMENT
: S(IS),
Offset(Off),
Size(S) {}
Iterator(const Iterator &) NOEXCEPT XRAY_NEVER_INSTRUMENT = default;
Iterator() NOEXCEPT XRAY_NEVER_INSTRUMENT = default;
Iterator(Iterator &&) NOEXCEPT XRAY_NEVER_INSTRUMENT = default;
Iterator &operator=(const Iterator &) XRAY_NEVER_INSTRUMENT = default;
Iterator &operator=(Iterator &&) XRAY_NEVER_INSTRUMENT = default;
~Iterator() XRAY_NEVER_INSTRUMENT = default;
Iterator &operator++() XRAY_NEVER_INSTRUMENT {
if (++Offset % ElementsPerSegment || Offset == Size)
return *this;
// At this point, we know that Offset % N == 0, so we must advance the
// segment pointer.
DCHECK_EQ(Offset % ElementsPerSegment, 0);
DCHECK_NE(Offset, Size);
DCHECK_NE(S, &SentinelSegment);
DCHECK_NE(S->Next, &SentinelSegment);
S = S->Next;
DCHECK_NE(S, &SentinelSegment);
return *this;
}
Iterator &operator--() XRAY_NEVER_INSTRUMENT {
DCHECK_NE(S, &SentinelSegment);
DCHECK_GT(Offset, 0);
auto PreviousOffset = Offset--;
if (PreviousOffset != Size && PreviousOffset % ElementsPerSegment == 0) {
DCHECK_NE(S->Prev, &SentinelSegment);
S = S->Prev;
}
return *this;
}
Iterator operator++(int) XRAY_NEVER_INSTRUMENT {
Iterator Copy(*this);
++(*this);
return Copy;
}
Iterator operator--(int) XRAY_NEVER_INSTRUMENT {
Iterator Copy(*this);
--(*this);
return Copy;
}
template <class V, class W>
friend bool operator==(const Iterator<V> &L,
const Iterator<W> &R) XRAY_NEVER_INSTRUMENT {
return L.S == R.S && L.Offset == R.Offset;
}
template <class V, class W>
friend bool operator!=(const Iterator<V> &L,
const Iterator<W> &R) XRAY_NEVER_INSTRUMENT {
return !(L == R);
}
U &operator*() const XRAY_NEVER_INSTRUMENT {
DCHECK_NE(S, &SentinelSegment);
auto RelOff = Offset % ElementsPerSegment;
// We need to compute the character-aligned pointer, offset from the
// segment's Data location to get the element in the position of Offset.
auto Base = &S->Data;
auto AlignedOffset = Base + (RelOff * AlignedElementStorageSize);
return *reinterpret_cast<U *>(AlignedOffset);
}
U *operator->() const XRAY_NEVER_INSTRUMENT { return &(**this); }
};
AllocatorType *Alloc;
Segment *Head;
Segment *Tail;
// Here we keep track of segments in the freelist, to allow us to re-use
// segments when elements are trimmed off the end.
Segment *Freelist;
uint64_t Size;
// ===============================
// In the following implementation, we work through the algorithms and the
// list operations using the following notation:
//
// - pred(s) is the predecessor (previous node accessor) and succ(s) is
// the successor (next node accessor).
//
// - S is a sentinel segment, which has the following property:
//
// pred(S) == succ(S) == S
//
// - @ is a loop operator, which can imply pred(s) == s if it appears on
// the left of s, or succ(s) == S if it appears on the right of s.
//
// - sL <-> sR : means a bidirectional relation between sL and sR, which
// means:
//
// succ(sL) == sR && pred(SR) == sL
//
// - sL -> sR : implies a unidirectional relation between sL and SR,
// with the following properties:
//
// succ(sL) == sR
//
// sL <- sR : implies a unidirectional relation between sR and sL,
// with the following properties:
//
// pred(sR) == sL
//
// ===============================
Segment *NewSegment() XRAY_NEVER_INSTRUMENT {
// We need to handle the case in which enough elements have been trimmed to
// allow us to re-use segments we've allocated before. For this we look into
// the Freelist, to see whether we need to actually allocate new blocks or
// just re-use blocks we've already seen before.
if (Freelist != &SentinelSegment) {
// The current state of lists resemble something like this at this point:
//
// Freelist: @S@<-f0->...<->fN->@S@
// ^ Freelist
//
// We want to perform a splice of `f0` from Freelist to a temporary list,
// which looks like:
//
// Templist: @S@<-f0->@S@
// ^ FreeSegment
//
// Our algorithm preconditions are:
DCHECK_EQ(Freelist->Prev, &SentinelSegment);
// Then the algorithm we implement is:
//
// SFS = Freelist
// Freelist = succ(Freelist)
// if (Freelist != S)
// pred(Freelist) = S
// succ(SFS) = S
// pred(SFS) = S
//
auto *FreeSegment = Freelist;
Freelist = Freelist->Next;
// Note that we need to handle the case where Freelist is now pointing to
// S, which we don't want to be overwriting.
// TODO: Determine whether the cost of the branch is higher than the cost
// of the blind assignment.
if (Freelist != &SentinelSegment)
Freelist->Prev = &SentinelSegment;
FreeSegment->Next = &SentinelSegment;
FreeSegment->Prev = &SentinelSegment;
// Our postconditions are:
DCHECK_EQ(Freelist->Prev, &SentinelSegment);
DCHECK_NE(FreeSegment, &SentinelSegment);
return FreeSegment;
}
auto SegmentBlock = Alloc->Allocate();
if (SegmentBlock.Data == nullptr)
return nullptr;
// Placement-new the Segment element at the beginning of the SegmentBlock.
new (SegmentBlock.Data) Segment{&SentinelSegment, &SentinelSegment, {0}};
auto SB = reinterpret_cast<Segment *>(SegmentBlock.Data);
return SB;
}
Segment *InitHeadAndTail() XRAY_NEVER_INSTRUMENT {
DCHECK_EQ(Head, &SentinelSegment);
DCHECK_EQ(Tail, &SentinelSegment);
auto S = NewSegment();
if (S == nullptr)
return nullptr;
DCHECK_EQ(S->Next, &SentinelSegment);
DCHECK_EQ(S->Prev, &SentinelSegment);
DCHECK_NE(S, &SentinelSegment);
Head = S;
Tail = S;
DCHECK_EQ(Head, Tail);
DCHECK_EQ(Tail->Next, &SentinelSegment);
DCHECK_EQ(Tail->Prev, &SentinelSegment);
return S;
}
Segment *AppendNewSegment() XRAY_NEVER_INSTRUMENT {
auto S = NewSegment();
if (S == nullptr)
return nullptr;
DCHECK_NE(Tail, &SentinelSegment);
DCHECK_EQ(Tail->Next, &SentinelSegment);
DCHECK_EQ(S->Prev, &SentinelSegment);
DCHECK_EQ(S->Next, &SentinelSegment);
S->Prev = Tail;
Tail->Next = S;
Tail = S;
DCHECK_EQ(S, S->Prev->Next);
DCHECK_EQ(Tail->Next, &SentinelSegment);
return S;
}
public:
explicit Array(AllocatorType &A) XRAY_NEVER_INSTRUMENT
: Alloc(&A),
Head(&SentinelSegment),
Tail(&SentinelSegment),
Freelist(&SentinelSegment),
Size(0) {}
Array() XRAY_NEVER_INSTRUMENT : Alloc(nullptr),
Head(&SentinelSegment),
Tail(&SentinelSegment),
Freelist(&SentinelSegment),
Size(0) {}
Array(const Array &) = delete;
Array &operator=(const Array &) = delete;
Array(Array &&O) XRAY_NEVER_INSTRUMENT : Alloc(O.Alloc),
Head(O.Head),
Tail(O.Tail),
Freelist(O.Freelist),
Size(O.Size) {
O.Alloc = nullptr;
O.Head = &SentinelSegment;
O.Tail = &SentinelSegment;
O.Size = 0;
O.Freelist = &SentinelSegment;
}
Array &operator=(Array &&O) XRAY_NEVER_INSTRUMENT {
Alloc = O.Alloc;
O.Alloc = nullptr;
Head = O.Head;
O.Head = &SentinelSegment;
Tail = O.Tail;
O.Tail = &SentinelSegment;
Freelist = O.Freelist;
O.Freelist = &SentinelSegment;
Size = O.Size;
O.Size = 0;
return *this;
}
~Array() XRAY_NEVER_INSTRUMENT {
for (auto &E : *this)
(&E)->~T();
}
bool empty() const XRAY_NEVER_INSTRUMENT { return Size == 0; }
AllocatorType &allocator() const XRAY_NEVER_INSTRUMENT {
DCHECK_NE(Alloc, nullptr);
return *Alloc;
}
uint64_t size() const XRAY_NEVER_INSTRUMENT { return Size; }
template <class... Args>
T *AppendEmplace(Args &&... args) XRAY_NEVER_INSTRUMENT {
DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) ||
(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));
if (UNLIKELY(Head == &SentinelSegment)) {
auto R = InitHeadAndTail();
if (R == nullptr)
return nullptr;
}
DCHECK_NE(Head, &SentinelSegment);
DCHECK_NE(Tail, &SentinelSegment);
auto Offset = Size % ElementsPerSegment;
if (UNLIKELY(Size != 0 && Offset == 0))
if (AppendNewSegment() == nullptr)
return nullptr;
DCHECK_NE(Tail, &SentinelSegment);
auto Base = &Tail->Data;
auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);
DCHECK_LE(AlignedOffset + sizeof(T),
reinterpret_cast<unsigned char *>(Base) + SegmentSize);
// In-place construct at Position.
new (AlignedOffset) T{std::forward<Args>(args)...};
++Size;
return reinterpret_cast<T *>(AlignedOffset);
}
T *Append(const T &E) XRAY_NEVER_INSTRUMENT {
// FIXME: This is a duplication of AppenEmplace with the copy semantics
// explicitly used, as a work-around to GCC 4.8 not invoking the copy
// constructor with the placement new with braced-init syntax.
DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) ||
(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));
if (UNLIKELY(Head == &SentinelSegment)) {
auto R = InitHeadAndTail();
if (R == nullptr)
return nullptr;
}
DCHECK_NE(Head, &SentinelSegment);
DCHECK_NE(Tail, &SentinelSegment);
auto Offset = Size % ElementsPerSegment;
if (UNLIKELY(Size != 0 && Offset == 0))
if (AppendNewSegment() == nullptr)
return nullptr;
DCHECK_NE(Tail, &SentinelSegment);
auto Base = &Tail->Data;
auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);
DCHECK_LE(AlignedOffset + sizeof(T),
reinterpret_cast<unsigned char *>(Tail) + SegmentSize);
// In-place construct at Position.
new (AlignedOffset) T(E);
++Size;
return reinterpret_cast<T *>(AlignedOffset);
}
T &operator[](uint64_t Offset) const XRAY_NEVER_INSTRUMENT {
DCHECK_LE(Offset, Size);
// We need to traverse the array enough times to find the element at Offset.
auto S = Head;
while (Offset >= ElementsPerSegment) {
S = S->Next;
Offset -= ElementsPerSegment;
DCHECK_NE(S, &SentinelSegment);
}
auto Base = &S->Data;
auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);
auto Position = reinterpret_cast<T *>(AlignedOffset);
return *reinterpret_cast<T *>(Position);
}
T &front() const XRAY_NEVER_INSTRUMENT {
DCHECK_NE(Head, &SentinelSegment);
DCHECK_NE(Size, 0u);
return *begin();
}
T &back() const XRAY_NEVER_INSTRUMENT {
DCHECK_NE(Tail, &SentinelSegment);
DCHECK_NE(Size, 0u);
auto It = end();
--It;
return *It;
}
template <class Predicate>
T *find_element(Predicate P) const XRAY_NEVER_INSTRUMENT {
if (empty())
return nullptr;
auto E = end();
for (auto I = begin(); I != E; ++I)
if (P(*I))
return &(*I);
return nullptr;
}
/// Remove N Elements from the end. This leaves the blocks behind, and not
/// require allocation of new blocks for new elements added after trimming.
void trim(uint64_t Elements) XRAY_NEVER_INSTRUMENT {
auto OldSize = Size;
Elements = Elements > Size ? Size : Elements;
Size -= Elements;
// We compute the number of segments we're going to return from the tail by
// counting how many elements have been trimmed. Given the following:
//
// - Each segment has N valid positions, where N > 0
// - The previous size > current size
//
// To compute the number of segments to return, we need to perform the
// following calculations for the number of segments required given 'x'
// elements:
//
// f(x) = {
// x == 0 : 0
// , 0 < x <= N : 1
// , N < x <= max : x / N + (x % N ? 1 : 0)
// }
//
// We can simplify this down to:
//
// f(x) = {
// x == 0 : 0,
// , 0 < x <= max : x / N + (x < N || x % N ? 1 : 0)
// }
//
// And further down to:
//
// f(x) = x ? x / N + (x < N || x % N ? 1 : 0) : 0
//
// We can then perform the following calculation `s` which counts the number
// of segments we need to remove from the end of the data structure:
//
// s(p, c) = f(p) - f(c)
//
// If we treat p = previous size, and c = current size, and given the
// properties above, the possible range for s(...) is [0..max(typeof(p))/N]
// given that typeof(p) == typeof(c).
auto F = [](uint64_t X) {
return X ? (X / ElementsPerSegment) +
(X < ElementsPerSegment || X % ElementsPerSegment ? 1 : 0)
: 0;
};
auto PS = F(OldSize);
auto CS = F(Size);
DCHECK_GE(PS, CS);
auto SegmentsToTrim = PS - CS;
for (auto I = 0uL; I < SegmentsToTrim; ++I) {
// Here we place the current tail segment to the freelist. To do this
// appropriately, we need to perform a splice operation on two
// bidirectional linked-lists. In particular, we have the current state of
// the doubly-linked list of segments:
//
// @S@ <- s0 <-> s1 <-> ... <-> sT -> @S@
//
DCHECK_NE(Head, &SentinelSegment);
DCHECK_NE(Tail, &SentinelSegment);
DCHECK_EQ(Tail->Next, &SentinelSegment);
if (Freelist == &SentinelSegment) {
// Our two lists at this point are in this configuration:
//
// Freelist: (potentially) @S@
// Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@
// ^ Head ^ Tail
//
// The end state for us will be this configuration:
//
// Freelist: @S@<-sT->@S@
// Mainlist: @S@<-s0<->s1<->...<->sPT->@S@
// ^ Head ^ Tail
//
// The first step for us is to hold a reference to the tail of Mainlist,
// which in our notation is represented by sT. We call this our "free
// segment" which is the segment we are placing on the Freelist.
//
// sF = sT
//
// Then, we also hold a reference to the "pre-tail" element, which we
// call sPT:
//
// sPT = pred(sT)
//
// We want to splice sT into the beginning of the Freelist, which in
// an empty Freelist means placing a segment whose predecessor and
// successor is the sentinel segment.
//
// The splice operation then can be performed in the following
// algorithm:
//
// succ(sPT) = S
// pred(sT) = S
// succ(sT) = Freelist
// Freelist = sT
// Tail = sPT
//
auto SPT = Tail->Prev;
SPT->Next = &SentinelSegment;
Tail->Prev = &SentinelSegment;
Tail->Next = Freelist;
Freelist = Tail;
Tail = SPT;
// Our post-conditions here are:
DCHECK_EQ(Tail->Next, &SentinelSegment);
DCHECK_EQ(Freelist->Prev, &SentinelSegment);
} else {
// In the other case, where the Freelist is not empty, we perform the
// following transformation instead:
//
// This transforms the current state:
//
// Freelist: @S@<-f0->@S@
// ^ Freelist
// Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@
// ^ Head ^ Tail
//
// Into the following:
//
// Freelist: @S@<-sT<->f0->@S@
// ^ Freelist
// Mainlist: @S@<-s0<->s1<->...<->sPT->@S@
// ^ Head ^ Tail
//
// The algorithm is:
//
// sFH = Freelist
// sPT = pred(sT)
// pred(SFH) = sT
// succ(sT) = Freelist
// pred(sT) = S
// succ(sPT) = S
// Tail = sPT
// Freelist = sT
//
auto SFH = Freelist;
auto SPT = Tail->Prev;
auto ST = Tail;
SFH->Prev = ST;
ST->Next = Freelist;
ST->Prev = &SentinelSegment;
SPT->Next = &SentinelSegment;
Tail = SPT;
Freelist = ST;
// Our post-conditions here are:
DCHECK_EQ(Tail->Next, &SentinelSegment);
DCHECK_EQ(Freelist->Prev, &SentinelSegment);
DCHECK_EQ(Freelist->Next->Prev, Freelist);
}
}
// Now in case we've spliced all the segments in the end, we ensure that the
// main list is "empty", or both the head and tail pointing to the sentinel
// segment.
if (Tail == &SentinelSegment)
Head = Tail;
DCHECK(
(Size == 0 && Head == &SentinelSegment && Tail == &SentinelSegment) ||
(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));
DCHECK(
(Freelist != &SentinelSegment && Freelist->Prev == &SentinelSegment) ||
(Freelist == &SentinelSegment && Tail->Next == &SentinelSegment));
}
// Provide iterators.
Iterator<T> begin() const XRAY_NEVER_INSTRUMENT {
return Iterator<T>(Head, 0, Size);
}
Iterator<T> end() const XRAY_NEVER_INSTRUMENT {
return Iterator<T>(Tail, Size, Size);
}
Iterator<const T> cbegin() const XRAY_NEVER_INSTRUMENT {
return Iterator<const T>(Head, 0, Size);
}
Iterator<const T> cend() const XRAY_NEVER_INSTRUMENT {
return Iterator<const T>(Tail, Size, Size);
}
};
// We need to have this storage definition out-of-line so that the compiler can
// ensure that storage for the SentinelSegment is defined and has a single
// address.
template <class T>
typename Array<T>::Segment Array<T>::SentinelSegment{
&Array<T>::SentinelSegment, &Array<T>::SentinelSegment, {'\0'}};
} // namespace __xray
#endif // XRAY_SEGMENTED_ARRAY_H