Path: blob/main/contrib/llvm-project/compiler-rt/lib/xray/xray_segmented_array.h
35265 views
//===-- xray_segmented_array.h ---------------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file is a part of XRay, a dynamic runtime instrumentation system.9//10// Defines the implementation of a segmented array, with fixed-size segments11// backing the segments.12//13//===----------------------------------------------------------------------===//14#ifndef XRAY_SEGMENTED_ARRAY_H15#define XRAY_SEGMENTED_ARRAY_H1617#include "sanitizer_common/sanitizer_allocator.h"18#include "xray_allocator.h"19#include "xray_utils.h"20#include <cassert>21#include <type_traits>22#include <utility>2324namespace __xray {2526/// The Array type provides an interface similar to std::vector<...> but does27/// not shrink in size. Once constructed, elements can be appended but cannot be28/// removed. The implementation is heavily dependent on the contract provided by29/// the Allocator type, in that all memory will be released when the Allocator30/// is destroyed. When an Array is destroyed, it will destroy elements in the31/// backing store but will not free the memory.32template <class T> class Array {33struct Segment {34Segment *Prev;35Segment *Next;36char Data[1];37};3839public:40// Each segment of the array will be laid out with the following assumptions:41//42// - Each segment will be on a cache-line address boundary (kCacheLineSize43// aligned).44//45// - The elements will be accessed through an aligned pointer, dependent on46// the alignment of T.47//48// - Each element is at least two-pointers worth from the beginning of the49// Segment, aligned properly, and the rest of the elements are accessed50// through appropriate alignment.51//52// We then compute the size of the segment to follow this logic:53//54// - Compute the number of elements that can fit within55// kCacheLineSize-multiple segments, minus the size of two pointers.56//57// - Request cacheline-multiple sized elements from the allocator.58static constexpr uint64_t AlignedElementStorageSize = sizeof(T);5960static constexpr uint64_t SegmentControlBlockSize = sizeof(Segment *) * 2;6162static constexpr uint64_t SegmentSize = nearest_boundary(63SegmentControlBlockSize + next_pow2(sizeof(T)), kCacheLineSize);6465using AllocatorType = Allocator<SegmentSize>;6667static constexpr uint64_t ElementsPerSegment =68(SegmentSize - SegmentControlBlockSize) / next_pow2(sizeof(T));6970static_assert(ElementsPerSegment > 0,71"Must have at least 1 element per segment.");7273static Segment SentinelSegment;7475using size_type = uint64_t;7677private:78// This Iterator models a BidirectionalIterator.79template <class U> class Iterator {80Segment *S = &SentinelSegment;81uint64_t Offset = 0;82uint64_t Size = 0;8384public:85Iterator(Segment *IS, uint64_t Off, uint64_t S) XRAY_NEVER_INSTRUMENT86: S(IS),87Offset(Off),88Size(S) {}89Iterator(const Iterator &) NOEXCEPT XRAY_NEVER_INSTRUMENT = default;90Iterator() NOEXCEPT XRAY_NEVER_INSTRUMENT = default;91Iterator(Iterator &&) NOEXCEPT XRAY_NEVER_INSTRUMENT = default;92Iterator &operator=(const Iterator &) XRAY_NEVER_INSTRUMENT = default;93Iterator &operator=(Iterator &&) XRAY_NEVER_INSTRUMENT = default;94~Iterator() XRAY_NEVER_INSTRUMENT = default;9596Iterator &operator++() XRAY_NEVER_INSTRUMENT {97if (++Offset % ElementsPerSegment || Offset == Size)98return *this;99100// At this point, we know that Offset % N == 0, so we must advance the101// segment pointer.102DCHECK_EQ(Offset % ElementsPerSegment, 0);103DCHECK_NE(Offset, Size);104DCHECK_NE(S, &SentinelSegment);105DCHECK_NE(S->Next, &SentinelSegment);106S = S->Next;107DCHECK_NE(S, &SentinelSegment);108return *this;109}110111Iterator &operator--() XRAY_NEVER_INSTRUMENT {112DCHECK_NE(S, &SentinelSegment);113DCHECK_GT(Offset, 0);114115auto PreviousOffset = Offset--;116if (PreviousOffset != Size && PreviousOffset % ElementsPerSegment == 0) {117DCHECK_NE(S->Prev, &SentinelSegment);118S = S->Prev;119}120121return *this;122}123124Iterator operator++(int) XRAY_NEVER_INSTRUMENT {125Iterator Copy(*this);126++(*this);127return Copy;128}129130Iterator operator--(int) XRAY_NEVER_INSTRUMENT {131Iterator Copy(*this);132--(*this);133return Copy;134}135136template <class V, class W>137friend bool operator==(const Iterator<V> &L,138const Iterator<W> &R) XRAY_NEVER_INSTRUMENT {139return L.S == R.S && L.Offset == R.Offset;140}141142template <class V, class W>143friend bool operator!=(const Iterator<V> &L,144const Iterator<W> &R) XRAY_NEVER_INSTRUMENT {145return !(L == R);146}147148U &operator*() const XRAY_NEVER_INSTRUMENT {149DCHECK_NE(S, &SentinelSegment);150auto RelOff = Offset % ElementsPerSegment;151152// We need to compute the character-aligned pointer, offset from the153// segment's Data location to get the element in the position of Offset.154auto Base = &S->Data;155auto AlignedOffset = Base + (RelOff * AlignedElementStorageSize);156return *reinterpret_cast<U *>(AlignedOffset);157}158159U *operator->() const XRAY_NEVER_INSTRUMENT { return &(**this); }160};161162AllocatorType *Alloc;163Segment *Head;164Segment *Tail;165166// Here we keep track of segments in the freelist, to allow us to re-use167// segments when elements are trimmed off the end.168Segment *Freelist;169uint64_t Size;170171// ===============================172// In the following implementation, we work through the algorithms and the173// list operations using the following notation:174//175// - pred(s) is the predecessor (previous node accessor) and succ(s) is176// the successor (next node accessor).177//178// - S is a sentinel segment, which has the following property:179//180// pred(S) == succ(S) == S181//182// - @ is a loop operator, which can imply pred(s) == s if it appears on183// the left of s, or succ(s) == S if it appears on the right of s.184//185// - sL <-> sR : means a bidirectional relation between sL and sR, which186// means:187//188// succ(sL) == sR && pred(SR) == sL189//190// - sL -> sR : implies a unidirectional relation between sL and SR,191// with the following properties:192//193// succ(sL) == sR194//195// sL <- sR : implies a unidirectional relation between sR and sL,196// with the following properties:197//198// pred(sR) == sL199//200// ===============================201202Segment *NewSegment() XRAY_NEVER_INSTRUMENT {203// We need to handle the case in which enough elements have been trimmed to204// allow us to re-use segments we've allocated before. For this we look into205// the Freelist, to see whether we need to actually allocate new blocks or206// just re-use blocks we've already seen before.207if (Freelist != &SentinelSegment) {208// The current state of lists resemble something like this at this point:209//210// Freelist: @S@<-f0->...<->fN->@S@211// ^ Freelist212//213// We want to perform a splice of `f0` from Freelist to a temporary list,214// which looks like:215//216// Templist: @S@<-f0->@S@217// ^ FreeSegment218//219// Our algorithm preconditions are:220DCHECK_EQ(Freelist->Prev, &SentinelSegment);221222// Then the algorithm we implement is:223//224// SFS = Freelist225// Freelist = succ(Freelist)226// if (Freelist != S)227// pred(Freelist) = S228// succ(SFS) = S229// pred(SFS) = S230//231auto *FreeSegment = Freelist;232Freelist = Freelist->Next;233234// Note that we need to handle the case where Freelist is now pointing to235// S, which we don't want to be overwriting.236// TODO: Determine whether the cost of the branch is higher than the cost237// of the blind assignment.238if (Freelist != &SentinelSegment)239Freelist->Prev = &SentinelSegment;240241FreeSegment->Next = &SentinelSegment;242FreeSegment->Prev = &SentinelSegment;243244// Our postconditions are:245DCHECK_EQ(Freelist->Prev, &SentinelSegment);246DCHECK_NE(FreeSegment, &SentinelSegment);247return FreeSegment;248}249250auto SegmentBlock = Alloc->Allocate();251if (SegmentBlock.Data == nullptr)252return nullptr;253254// Placement-new the Segment element at the beginning of the SegmentBlock.255new (SegmentBlock.Data) Segment{&SentinelSegment, &SentinelSegment, {0}};256auto SB = reinterpret_cast<Segment *>(SegmentBlock.Data);257return SB;258}259260Segment *InitHeadAndTail() XRAY_NEVER_INSTRUMENT {261DCHECK_EQ(Head, &SentinelSegment);262DCHECK_EQ(Tail, &SentinelSegment);263auto S = NewSegment();264if (S == nullptr)265return nullptr;266DCHECK_EQ(S->Next, &SentinelSegment);267DCHECK_EQ(S->Prev, &SentinelSegment);268DCHECK_NE(S, &SentinelSegment);269Head = S;270Tail = S;271DCHECK_EQ(Head, Tail);272DCHECK_EQ(Tail->Next, &SentinelSegment);273DCHECK_EQ(Tail->Prev, &SentinelSegment);274return S;275}276277Segment *AppendNewSegment() XRAY_NEVER_INSTRUMENT {278auto S = NewSegment();279if (S == nullptr)280return nullptr;281DCHECK_NE(Tail, &SentinelSegment);282DCHECK_EQ(Tail->Next, &SentinelSegment);283DCHECK_EQ(S->Prev, &SentinelSegment);284DCHECK_EQ(S->Next, &SentinelSegment);285S->Prev = Tail;286Tail->Next = S;287Tail = S;288DCHECK_EQ(S, S->Prev->Next);289DCHECK_EQ(Tail->Next, &SentinelSegment);290return S;291}292293public:294explicit Array(AllocatorType &A) XRAY_NEVER_INSTRUMENT295: Alloc(&A),296Head(&SentinelSegment),297Tail(&SentinelSegment),298Freelist(&SentinelSegment),299Size(0) {}300301Array() XRAY_NEVER_INSTRUMENT : Alloc(nullptr),302Head(&SentinelSegment),303Tail(&SentinelSegment),304Freelist(&SentinelSegment),305Size(0) {}306307Array(const Array &) = delete;308Array &operator=(const Array &) = delete;309310Array(Array &&O) XRAY_NEVER_INSTRUMENT : Alloc(O.Alloc),311Head(O.Head),312Tail(O.Tail),313Freelist(O.Freelist),314Size(O.Size) {315O.Alloc = nullptr;316O.Head = &SentinelSegment;317O.Tail = &SentinelSegment;318O.Size = 0;319O.Freelist = &SentinelSegment;320}321322Array &operator=(Array &&O) XRAY_NEVER_INSTRUMENT {323Alloc = O.Alloc;324O.Alloc = nullptr;325Head = O.Head;326O.Head = &SentinelSegment;327Tail = O.Tail;328O.Tail = &SentinelSegment;329Freelist = O.Freelist;330O.Freelist = &SentinelSegment;331Size = O.Size;332O.Size = 0;333return *this;334}335336~Array() XRAY_NEVER_INSTRUMENT {337for (auto &E : *this)338(&E)->~T();339}340341bool empty() const XRAY_NEVER_INSTRUMENT { return Size == 0; }342343AllocatorType &allocator() const XRAY_NEVER_INSTRUMENT {344DCHECK_NE(Alloc, nullptr);345return *Alloc;346}347348uint64_t size() const XRAY_NEVER_INSTRUMENT { return Size; }349350template <class... Args>351T *AppendEmplace(Args &&... args) XRAY_NEVER_INSTRUMENT {352DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) ||353(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));354if (UNLIKELY(Head == &SentinelSegment)) {355auto R = InitHeadAndTail();356if (R == nullptr)357return nullptr;358}359360DCHECK_NE(Head, &SentinelSegment);361DCHECK_NE(Tail, &SentinelSegment);362363auto Offset = Size % ElementsPerSegment;364if (UNLIKELY(Size != 0 && Offset == 0))365if (AppendNewSegment() == nullptr)366return nullptr;367368DCHECK_NE(Tail, &SentinelSegment);369auto Base = &Tail->Data;370auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);371DCHECK_LE(AlignedOffset + sizeof(T),372reinterpret_cast<unsigned char *>(Base) + SegmentSize);373374// In-place construct at Position.375new (AlignedOffset) T{std::forward<Args>(args)...};376++Size;377return reinterpret_cast<T *>(AlignedOffset);378}379380T *Append(const T &E) XRAY_NEVER_INSTRUMENT {381// FIXME: This is a duplication of AppenEmplace with the copy semantics382// explicitly used, as a work-around to GCC 4.8 not invoking the copy383// constructor with the placement new with braced-init syntax.384DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) ||385(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));386if (UNLIKELY(Head == &SentinelSegment)) {387auto R = InitHeadAndTail();388if (R == nullptr)389return nullptr;390}391392DCHECK_NE(Head, &SentinelSegment);393DCHECK_NE(Tail, &SentinelSegment);394395auto Offset = Size % ElementsPerSegment;396if (UNLIKELY(Size != 0 && Offset == 0))397if (AppendNewSegment() == nullptr)398return nullptr;399400DCHECK_NE(Tail, &SentinelSegment);401auto Base = &Tail->Data;402auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);403DCHECK_LE(AlignedOffset + sizeof(T),404reinterpret_cast<unsigned char *>(Tail) + SegmentSize);405406// In-place construct at Position.407new (AlignedOffset) T(E);408++Size;409return reinterpret_cast<T *>(AlignedOffset);410}411412T &operator[](uint64_t Offset) const XRAY_NEVER_INSTRUMENT {413DCHECK_LE(Offset, Size);414// We need to traverse the array enough times to find the element at Offset.415auto S = Head;416while (Offset >= ElementsPerSegment) {417S = S->Next;418Offset -= ElementsPerSegment;419DCHECK_NE(S, &SentinelSegment);420}421auto Base = &S->Data;422auto AlignedOffset = Base + (Offset * AlignedElementStorageSize);423auto Position = reinterpret_cast<T *>(AlignedOffset);424return *reinterpret_cast<T *>(Position);425}426427T &front() const XRAY_NEVER_INSTRUMENT {428DCHECK_NE(Head, &SentinelSegment);429DCHECK_NE(Size, 0u);430return *begin();431}432433T &back() const XRAY_NEVER_INSTRUMENT {434DCHECK_NE(Tail, &SentinelSegment);435DCHECK_NE(Size, 0u);436auto It = end();437--It;438return *It;439}440441template <class Predicate>442T *find_element(Predicate P) const XRAY_NEVER_INSTRUMENT {443if (empty())444return nullptr;445446auto E = end();447for (auto I = begin(); I != E; ++I)448if (P(*I))449return &(*I);450451return nullptr;452}453454/// Remove N Elements from the end. This leaves the blocks behind, and not455/// require allocation of new blocks for new elements added after trimming.456void trim(uint64_t Elements) XRAY_NEVER_INSTRUMENT {457auto OldSize = Size;458Elements = Elements > Size ? Size : Elements;459Size -= Elements;460461// We compute the number of segments we're going to return from the tail by462// counting how many elements have been trimmed. Given the following:463//464// - Each segment has N valid positions, where N > 0465// - The previous size > current size466//467// To compute the number of segments to return, we need to perform the468// following calculations for the number of segments required given 'x'469// elements:470//471// f(x) = {472// x == 0 : 0473// , 0 < x <= N : 1474// , N < x <= max : x / N + (x % N ? 1 : 0)475// }476//477// We can simplify this down to:478//479// f(x) = {480// x == 0 : 0,481// , 0 < x <= max : x / N + (x < N || x % N ? 1 : 0)482// }483//484// And further down to:485//486// f(x) = x ? x / N + (x < N || x % N ? 1 : 0) : 0487//488// We can then perform the following calculation `s` which counts the number489// of segments we need to remove from the end of the data structure:490//491// s(p, c) = f(p) - f(c)492//493// If we treat p = previous size, and c = current size, and given the494// properties above, the possible range for s(...) is [0..max(typeof(p))/N]495// given that typeof(p) == typeof(c).496auto F = [](uint64_t X) {497return X ? (X / ElementsPerSegment) +498(X < ElementsPerSegment || X % ElementsPerSegment ? 1 : 0)499: 0;500};501auto PS = F(OldSize);502auto CS = F(Size);503DCHECK_GE(PS, CS);504auto SegmentsToTrim = PS - CS;505for (auto I = 0uL; I < SegmentsToTrim; ++I) {506// Here we place the current tail segment to the freelist. To do this507// appropriately, we need to perform a splice operation on two508// bidirectional linked-lists. In particular, we have the current state of509// the doubly-linked list of segments:510//511// @S@ <- s0 <-> s1 <-> ... <-> sT -> @S@512//513DCHECK_NE(Head, &SentinelSegment);514DCHECK_NE(Tail, &SentinelSegment);515DCHECK_EQ(Tail->Next, &SentinelSegment);516517if (Freelist == &SentinelSegment) {518// Our two lists at this point are in this configuration:519//520// Freelist: (potentially) @S@521// Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@522// ^ Head ^ Tail523//524// The end state for us will be this configuration:525//526// Freelist: @S@<-sT->@S@527// Mainlist: @S@<-s0<->s1<->...<->sPT->@S@528// ^ Head ^ Tail529//530// The first step for us is to hold a reference to the tail of Mainlist,531// which in our notation is represented by sT. We call this our "free532// segment" which is the segment we are placing on the Freelist.533//534// sF = sT535//536// Then, we also hold a reference to the "pre-tail" element, which we537// call sPT:538//539// sPT = pred(sT)540//541// We want to splice sT into the beginning of the Freelist, which in542// an empty Freelist means placing a segment whose predecessor and543// successor is the sentinel segment.544//545// The splice operation then can be performed in the following546// algorithm:547//548// succ(sPT) = S549// pred(sT) = S550// succ(sT) = Freelist551// Freelist = sT552// Tail = sPT553//554auto SPT = Tail->Prev;555SPT->Next = &SentinelSegment;556Tail->Prev = &SentinelSegment;557Tail->Next = Freelist;558Freelist = Tail;559Tail = SPT;560561// Our post-conditions here are:562DCHECK_EQ(Tail->Next, &SentinelSegment);563DCHECK_EQ(Freelist->Prev, &SentinelSegment);564} else {565// In the other case, where the Freelist is not empty, we perform the566// following transformation instead:567//568// This transforms the current state:569//570// Freelist: @S@<-f0->@S@571// ^ Freelist572// Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@573// ^ Head ^ Tail574//575// Into the following:576//577// Freelist: @S@<-sT<->f0->@S@578// ^ Freelist579// Mainlist: @S@<-s0<->s1<->...<->sPT->@S@580// ^ Head ^ Tail581//582// The algorithm is:583//584// sFH = Freelist585// sPT = pred(sT)586// pred(SFH) = sT587// succ(sT) = Freelist588// pred(sT) = S589// succ(sPT) = S590// Tail = sPT591// Freelist = sT592//593auto SFH = Freelist;594auto SPT = Tail->Prev;595auto ST = Tail;596SFH->Prev = ST;597ST->Next = Freelist;598ST->Prev = &SentinelSegment;599SPT->Next = &SentinelSegment;600Tail = SPT;601Freelist = ST;602603// Our post-conditions here are:604DCHECK_EQ(Tail->Next, &SentinelSegment);605DCHECK_EQ(Freelist->Prev, &SentinelSegment);606DCHECK_EQ(Freelist->Next->Prev, Freelist);607}608}609610// Now in case we've spliced all the segments in the end, we ensure that the611// main list is "empty", or both the head and tail pointing to the sentinel612// segment.613if (Tail == &SentinelSegment)614Head = Tail;615616DCHECK(617(Size == 0 && Head == &SentinelSegment && Tail == &SentinelSegment) ||618(Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment));619DCHECK(620(Freelist != &SentinelSegment && Freelist->Prev == &SentinelSegment) ||621(Freelist == &SentinelSegment && Tail->Next == &SentinelSegment));622}623624// Provide iterators.625Iterator<T> begin() const XRAY_NEVER_INSTRUMENT {626return Iterator<T>(Head, 0, Size);627}628Iterator<T> end() const XRAY_NEVER_INSTRUMENT {629return Iterator<T>(Tail, Size, Size);630}631Iterator<const T> cbegin() const XRAY_NEVER_INSTRUMENT {632return Iterator<const T>(Head, 0, Size);633}634Iterator<const T> cend() const XRAY_NEVER_INSTRUMENT {635return Iterator<const T>(Tail, Size, Size);636}637};638639// We need to have this storage definition out-of-line so that the compiler can640// ensure that storage for the SentinelSegment is defined and has a single641// address.642template <class T>643typename Array<T>::Segment Array<T>::SentinelSegment{644&Array<T>::SentinelSegment, &Array<T>::SentinelSegment, {'\0'}};645646} // namespace __xray647648#endif // XRAY_SEGMENTED_ARRAY_H649650651