summaryrefslogtreecommitdiffstats
path: root/compiler-rt/lib/xray/xray_segmented_array.h
diff options
context:
space:
mode:
Diffstat (limited to 'compiler-rt/lib/xray/xray_segmented_array.h')
-rw-r--r--compiler-rt/lib/xray/xray_segmented_array.h337
1 files changed, 337 insertions, 0 deletions
diff --git a/compiler-rt/lib/xray/xray_segmented_array.h b/compiler-rt/lib/xray/xray_segmented_array.h
new file mode 100644
index 00000000000..a6ea31009c2
--- /dev/null
+++ b/compiler-rt/lib/xray/xray_segmented_array.h
@@ -0,0 +1,337 @@
+//===-- xray_segmented_array.h ---------------------------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of XRay, a dynamic runtime instrumentation system.
+//
+// Defines the implementation of a segmented array, with fixed-size chunks
+// backing the segments.
+//
+//===----------------------------------------------------------------------===//
+#ifndef XRAY_SEGMENTED_ARRAY_H
+#define XRAY_SEGMENTED_ARRAY_H
+
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "xray_allocator.h"
+#include <type_traits>
+#include <utility>
+
+namespace __xray {
+
+namespace {
+
+constexpr size_t gcd(size_t a, size_t b) {
+ return (b == 0) ? a : gcd(b, a % b);
+}
+
+constexpr size_t lcm(size_t a, size_t b) { return a * b / gcd(a, b); }
+
+} // namespace
+
+/// 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. The parameter N defines how many
+/// elements of T there should be in a single block.
+///
+/// We compute the least common multiple of the size of T and the cache line
+/// size, to allow us to maximise the number of T objects we can place in
+/// cache-line multiple sized blocks. To get back the number of T's, we divide
+/// this least common multiple by the size of T.
+template <class T, size_t N = lcm(sizeof(T), kCacheLineSize) / sizeof(T)>
+struct Array {
+ static constexpr size_t ChunkSize = N;
+ static constexpr size_t AllocatorChunkSize = sizeof(T) * ChunkSize;
+ using AllocatorType = Allocator<AllocatorChunkSize>;
+ static_assert(std::is_trivially_destructible<T>::value,
+ "T must be trivially destructible.");
+
+private:
+ // TODO: Consider co-locating the chunk information with the data in the
+ // Block, as in an intrusive list -- i.e. putting the next and previous
+ // pointer values inside the Block storage.
+ struct Chunk {
+ typename AllocatorType::Block Block;
+ static constexpr size_t Size = N;
+ Chunk *Prev = nullptr;
+ Chunk *Next = nullptr;
+ };
+
+ static Chunk SentinelChunk;
+
+ AllocatorType *Allocator;
+ Chunk *Head = &SentinelChunk;
+ Chunk *Tail = &SentinelChunk;
+ size_t Size = 0;
+ size_t FreeElements = 0;
+
+ // Here we keep track of chunks in the freelist, to allow us to re-use chunks
+ // when elements are trimmed off the end.
+ Chunk *Freelist = &SentinelChunk;
+
+ Chunk *NewChunk() {
+ // We need to handle the case in which enough elements have been trimmed to
+ // allow us to re-use chunks 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 != &SentinelChunk) {
+ auto *FreeChunk = Freelist;
+ Freelist = FreeChunk->Next;
+ FreeChunk->Next = &SentinelChunk;
+ return FreeChunk;
+ }
+
+ auto Block = Allocator->Allocate();
+ if (Block.Data == nullptr)
+ return nullptr;
+ // TODO: Maybe use a separate managed allocator for Chunk instances?
+ auto C = reinterpret_cast<Chunk *>(InternalAlloc(sizeof(Chunk)));
+ if (C == nullptr)
+ return nullptr;
+ C->Block = Block;
+ return C;
+ }
+
+ static AllocatorType &GetGlobalAllocator() {
+ static AllocatorType *const GlobalAllocator = [] {
+ AllocatorType *A = reinterpret_cast<AllocatorType *>(
+ InternalAlloc(sizeof(AllocatorType)));
+ new (A) AllocatorType(2 << 10, 0);
+ return A;
+ }();
+
+ return *GlobalAllocator;
+ }
+
+ Chunk *InitHeadAndTail() {
+ DCHECK_EQ(Head, &SentinelChunk);
+ DCHECK_EQ(Tail, &SentinelChunk);
+ auto Chunk = NewChunk();
+ if (Chunk == nullptr)
+ return nullptr;
+ Chunk->Prev = &SentinelChunk;
+ Chunk->Next = &SentinelChunk;
+ Head = Chunk;
+ Tail = Chunk;
+ return Chunk;
+ }
+
+ Chunk *AppendNewChunk() {
+ auto Chunk = NewChunk();
+ if (Chunk == nullptr)
+ return nullptr;
+ Tail->Next = Chunk;
+ Chunk->Prev = Tail;
+ Chunk->Next = &SentinelChunk;
+ Tail = Chunk;
+ return Chunk;
+ }
+
+ // This Iterator models a BidirectionalIterator.
+ template <class U> class Iterator {
+ Chunk *C = nullptr;
+ size_t Offset = 0;
+
+ public:
+ Iterator(Chunk *IC, size_t Off) : C(IC), Offset(Off) {}
+
+ Iterator &operator++() {
+ DCHECK_NE(C, &SentinelChunk);
+ if (++Offset % N)
+ return *this;
+
+ // At this point, we know that Offset % N == 0, so we must advance the
+ // chunk pointer.
+ DCHECK_EQ(Offset % N, 0);
+ C = C->Next;
+ return *this;
+ }
+
+ Iterator &operator--() {
+ DCHECK_NE(C, &SentinelChunk);
+ DCHECK_GT(Offset, 0);
+
+ // We check whether the offset was on a boundary before decrement, to see
+ // whether we need to retreat to the previous chunk.
+ if ((Offset-- % N) == 0)
+ C = C->Prev;
+ return *this;
+ }
+
+ Iterator operator++(int) {
+ Iterator Copy(*this);
+ ++(*this);
+ return Copy;
+ }
+
+ Iterator operator--(int) {
+ Iterator Copy(*this);
+ --(*this);
+ return Copy;
+ }
+
+ template <class V, class W>
+ friend bool operator==(const Iterator<V> &L, const Iterator<W> &R) {
+ return L.C == R.C && L.Offset == R.Offset;
+ }
+
+ template <class V, class W>
+ friend bool operator!=(const Iterator<V> &L, const Iterator<W> &R) {
+ return !(L == R);
+ }
+
+ U &operator*() const {
+ DCHECK_NE(C, &SentinelChunk);
+ return reinterpret_cast<U *>(C->Block.Data)[Offset % N];
+ }
+
+ U *operator->() const {
+ DCHECK_NE(C, &SentinelChunk);
+ return reinterpret_cast<U *>(C->Block.Data) + (Offset % N);
+ }
+ };
+
+public:
+ explicit Array(AllocatorType &A) : Allocator(&A) {}
+ Array() : Array(GetGlobalAllocator()) {}
+
+ Array(const Array &) = delete;
+ Array(Array &&O) NOEXCEPT : Allocator(O.Allocator),
+ Head(O.Head),
+ Tail(O.Tail),
+ Size(O.Size) {
+ O.Head = &SentinelChunk;
+ O.Tail = &SentinelChunk;
+ O.Size = 0;
+ }
+
+ bool empty() const { return Size == 0; }
+
+ AllocatorType &allocator() const {
+ DCHECK_NE(Allocator, nullptr);
+ return *Allocator;
+ }
+
+ size_t size() const { return Size; }
+
+ T *Append(const T &E) {
+ if (UNLIKELY(Head == &SentinelChunk))
+ if (InitHeadAndTail() == nullptr)
+ return nullptr;
+
+ auto Offset = Size % N;
+ if (UNLIKELY(Size != 0 && Offset == 0))
+ if (AppendNewChunk() == nullptr)
+ return nullptr;
+
+ auto Position = reinterpret_cast<T *>(Tail->Block.Data) + Offset;
+ *Position = E;
+ ++Size;
+ FreeElements -= FreeElements ? 1 : 0;
+ return Position;
+ }
+
+ template <class... Args> T *AppendEmplace(Args &&... args) {
+ if (UNLIKELY(Head == &SentinelChunk))
+ if (InitHeadAndTail() == nullptr)
+ return nullptr;
+
+ auto Offset = Size % N;
+ if (UNLIKELY(Size != 0 && Offset == 0))
+ if (AppendNewChunk() == nullptr)
+ return nullptr;
+
+ auto Position = reinterpret_cast<T *>(Tail->Block.Data) + Offset;
+ // In-place construct at Position.
+ new (Position) T(std::forward<Args>(args)...);
+ ++Size;
+ FreeElements -= FreeElements ? 1 : 0;
+ return Position;
+ }
+
+ T &operator[](size_t Offset) const {
+ DCHECK_LE(Offset, Size);
+ // We need to traverse the array enough times to find the element at Offset.
+ auto C = Head;
+ while (Offset >= N) {
+ C = C->Next;
+ Offset -= N;
+ DCHECK_NE(C, &SentinelChunk);
+ }
+ auto Position = reinterpret_cast<T *>(C->Block.Data) + Offset;
+ return *Position;
+ }
+
+ T &front() const {
+ DCHECK_NE(Head, &SentinelChunk);
+ DCHECK_NE(Size, 0u);
+ return *reinterpret_cast<T *>(Head->Block.Data);
+ }
+
+ T &back() const {
+ DCHECK_NE(Tail, &SentinelChunk);
+ auto Offset = (Size - 1) % N;
+ return *(reinterpret_cast<T *>(Tail->Block.Data) + Offset);
+ }
+
+ template <class Predicate> T *find_element(Predicate P) const {
+ 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(size_t Elements) {
+ DCHECK_LE(Elements, Size);
+ Size -= Elements;
+ FreeElements += Elements;
+
+ // Here we need to check whether we've cleared enough elements to warrant
+ // putting blocks on to the freelist. We determine whether we need to
+ // right-size the internal list, by keeping track of the number of "free"
+ // elements still in the array.
+ auto ChunksToTrim = FreeElements / N;
+ for (size_t i = 0; i < ChunksToTrim; ++i, FreeElements -= N) {
+ // Put the tail into the Freelist.
+ auto *FreeChunk = Tail;
+ Tail = Tail->Prev;
+ if (Tail == &SentinelChunk)
+ Head = Tail;
+ else
+ Tail->Next = &SentinelChunk;
+ FreeChunk->Next = Freelist;
+ FreeChunk->Prev = Freelist->Prev;
+ Freelist = FreeChunk;
+ }
+ }
+
+ // Provide iterators.
+ Iterator<T> begin() const { return Iterator<T>(Head, 0); }
+ Iterator<T> end() const { return Iterator<T>(Tail, Size); }
+ Iterator<const T> cbegin() const { return Iterator<const T>(Head, 0); }
+ Iterator<const T> cend() const { return Iterator<const T>(Tail, Size); }
+};
+
+// We need to have this storage definition out-of-line so that the compiler can
+// ensure that storage for the SentinelChunk is defined and has a single
+// address.
+template <class T, size_t N>
+typename Array<T, N>::Chunk Array<T, N>::SentinelChunk;
+
+} // namespace __xray
+
+#endif // XRAY_SEGMENTED_ARRAY_H
OpenPOWER on IntegriCloud