1 //===-- xray_segmented_array.h ---------------------------------*- C++ -*-===// 2 // 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 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of XRay, a dynamic runtime instrumentation system. 10 // 11 // Defines the implementation of a segmented array, with fixed-size segments 12 // backing the segments. 13 // 14 //===----------------------------------------------------------------------===// 15 #ifndef XRAY_SEGMENTED_ARRAY_H 16 #define XRAY_SEGMENTED_ARRAY_H 17 18 #include "sanitizer_common/sanitizer_allocator.h" 19 #include "xray_allocator.h" 20 #include "xray_utils.h" 21 #include <cassert> 22 #include <type_traits> 23 #include <utility> 24 25 namespace __xray { 26 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 { 34 struct Segment { 35 Segment *Prev; 36 Segment *Next; 37 char Data[1]; 38 }; 39 40 public: 41 // Each segment of the array will be laid out with the following assumptions: 42 // 43 // - Each segment will be on a cache-line address boundary (kCacheLineSize 44 // aligned). 45 // 46 // - The elements will be accessed through an aligned pointer, dependent on 47 // the alignment of T. 48 // 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. 52 // 53 // We then compute the size of the segment to follow this logic: 54 // 55 // - Compute the number of elements that can fit within 56 // kCacheLineSize-multiple segments, minus the size of two pointers. 57 // 58 // - Request cacheline-multiple sized elements from the allocator. 59 static constexpr uint64_t AlignedElementStorageSize = 60 sizeof(typename std::aligned_storage<sizeof(T), alignof(T)>::type); 61 62 static constexpr uint64_t SegmentControlBlockSize = sizeof(Segment *) * 2; 63 64 static constexpr uint64_t SegmentSize = nearest_boundary( 65 SegmentControlBlockSize + next_pow2(sizeof(T)), kCacheLineSize); 66 67 using AllocatorType = Allocator<SegmentSize>; 68 69 static constexpr uint64_t ElementsPerSegment = 70 (SegmentSize - SegmentControlBlockSize) / next_pow2(sizeof(T)); 71 72 static_assert(ElementsPerSegment > 0, 73 "Must have at least 1 element per segment."); 74 75 static Segment SentinelSegment; 76 77 using size_type = uint64_t; 78 79 private: 80 // This Iterator models a BidirectionalIterator. 81 template <class U> class Iterator { 82 Segment *S = &SentinelSegment; 83 uint64_t Offset = 0; 84 uint64_t Size = 0; 85 86 public: 87 Iterator(Segment *IS, uint64_t Off, uint64_t S) XRAY_NEVER_INSTRUMENT 88 : S(IS), 89 Offset(Off), 90 Size(S) {} 91 Iterator(const Iterator &) NOEXCEPT XRAY_NEVER_INSTRUMENT = default; 92 Iterator() NOEXCEPT XRAY_NEVER_INSTRUMENT = default; 93 Iterator(Iterator &&) NOEXCEPT XRAY_NEVER_INSTRUMENT = default; 94 Iterator &operator=(const Iterator &) XRAY_NEVER_INSTRUMENT = default; 95 Iterator &operator=(Iterator &&) XRAY_NEVER_INSTRUMENT = default; 96 ~Iterator() XRAY_NEVER_INSTRUMENT = default; 97 98 Iterator &operator++() XRAY_NEVER_INSTRUMENT { 99 if (++Offset % ElementsPerSegment || Offset == Size) 100 return *this; 101 102 // At this point, we know that Offset % N == 0, so we must advance the 103 // segment pointer. 104 DCHECK_EQ(Offset % ElementsPerSegment, 0); 105 DCHECK_NE(Offset, Size); 106 DCHECK_NE(S, &SentinelSegment); 107 DCHECK_NE(S->Next, &SentinelSegment); 108 S = S->Next; 109 DCHECK_NE(S, &SentinelSegment); 110 return *this; 111 } 112 113 Iterator &operator--() XRAY_NEVER_INSTRUMENT { 114 DCHECK_NE(S, &SentinelSegment); 115 DCHECK_GT(Offset, 0); 116 117 auto PreviousOffset = Offset--; 118 if (PreviousOffset != Size && PreviousOffset % ElementsPerSegment == 0) { 119 DCHECK_NE(S->Prev, &SentinelSegment); 120 S = S->Prev; 121 } 122 123 return *this; 124 } 125 126 Iterator operator++(int) XRAY_NEVER_INSTRUMENT { 127 Iterator Copy(*this); 128 ++(*this); 129 return Copy; 130 } 131 132 Iterator operator--(int) XRAY_NEVER_INSTRUMENT { 133 Iterator Copy(*this); 134 --(*this); 135 return Copy; 136 } 137 138 template <class V, class W> 139 friend bool operator==(const Iterator<V> &L, 140 const Iterator<W> &R) XRAY_NEVER_INSTRUMENT { 141 return L.S == R.S && L.Offset == R.Offset; 142 } 143 144 template <class V, class W> 145 friend bool operator!=(const Iterator<V> &L, 146 const Iterator<W> &R) XRAY_NEVER_INSTRUMENT { 147 return !(L == R); 148 } 149 150 U &operator*() const XRAY_NEVER_INSTRUMENT { 151 DCHECK_NE(S, &SentinelSegment); 152 auto RelOff = Offset % ElementsPerSegment; 153 154 // We need to compute the character-aligned pointer, offset from the 155 // segment's Data location to get the element in the position of Offset. 156 auto Base = &S->Data; 157 auto AlignedOffset = Base + (RelOff * AlignedElementStorageSize); 158 return *reinterpret_cast<U *>(AlignedOffset); 159 } 160 161 U *operator->() const XRAY_NEVER_INSTRUMENT { return &(**this); } 162 }; 163 164 AllocatorType *Alloc; 165 Segment *Head; 166 Segment *Tail; 167 168 // Here we keep track of segments in the freelist, to allow us to re-use 169 // segments when elements are trimmed off the end. 170 Segment *Freelist; 171 uint64_t Size; 172 173 // =============================== 174 // In the following implementation, we work through the algorithms and the 175 // list operations using the following notation: 176 // 177 // - pred(s) is the predecessor (previous node accessor) and succ(s) is 178 // the successor (next node accessor). 179 // 180 // - S is a sentinel segment, which has the following property: 181 // 182 // pred(S) == succ(S) == S 183 // 184 // - @ is a loop operator, which can imply pred(s) == s if it appears on 185 // the left of s, or succ(s) == S if it appears on the right of s. 186 // 187 // - sL <-> sR : means a bidirectional relation between sL and sR, which 188 // means: 189 // 190 // succ(sL) == sR && pred(SR) == sL 191 // 192 // - sL -> sR : implies a unidirectional relation between sL and SR, 193 // with the following properties: 194 // 195 // succ(sL) == sR 196 // 197 // sL <- sR : implies a unidirectional relation between sR and sL, 198 // with the following properties: 199 // 200 // pred(sR) == sL 201 // 202 // =============================== 203 204 Segment *NewSegment() XRAY_NEVER_INSTRUMENT { 205 // We need to handle the case in which enough elements have been trimmed to 206 // allow us to re-use segments we've allocated before. For this we look into 207 // the Freelist, to see whether we need to actually allocate new blocks or 208 // just re-use blocks we've already seen before. 209 if (Freelist != &SentinelSegment) { 210 // The current state of lists resemble something like this at this point: 211 // 212 // Freelist: @S@<-f0->...<->fN->@S@ 213 // ^ Freelist 214 // 215 // We want to perform a splice of `f0` from Freelist to a temporary list, 216 // which looks like: 217 // 218 // Templist: @S@<-f0->@S@ 219 // ^ FreeSegment 220 // 221 // Our algorithm preconditions are: 222 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 223 224 // Then the algorithm we implement is: 225 // 226 // SFS = Freelist 227 // Freelist = succ(Freelist) 228 // if (Freelist != S) 229 // pred(Freelist) = S 230 // succ(SFS) = S 231 // pred(SFS) = S 232 // 233 auto *FreeSegment = Freelist; 234 Freelist = Freelist->Next; 235 236 // Note that we need to handle the case where Freelist is now pointing to 237 // S, which we don't want to be overwriting. 238 // TODO: Determine whether the cost of the branch is higher than the cost 239 // of the blind assignment. 240 if (Freelist != &SentinelSegment) 241 Freelist->Prev = &SentinelSegment; 242 243 FreeSegment->Next = &SentinelSegment; 244 FreeSegment->Prev = &SentinelSegment; 245 246 // Our postconditions are: 247 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 248 DCHECK_NE(FreeSegment, &SentinelSegment); 249 return FreeSegment; 250 } 251 252 auto SegmentBlock = Alloc->Allocate(); 253 if (SegmentBlock.Data == nullptr) 254 return nullptr; 255 256 // Placement-new the Segment element at the beginning of the SegmentBlock. 257 new (SegmentBlock.Data) Segment{&SentinelSegment, &SentinelSegment, {0}}; 258 auto SB = reinterpret_cast<Segment *>(SegmentBlock.Data); 259 return SB; 260 } 261 262 Segment *InitHeadAndTail() XRAY_NEVER_INSTRUMENT { 263 DCHECK_EQ(Head, &SentinelSegment); 264 DCHECK_EQ(Tail, &SentinelSegment); 265 auto S = NewSegment(); 266 if (S == nullptr) 267 return nullptr; 268 DCHECK_EQ(S->Next, &SentinelSegment); 269 DCHECK_EQ(S->Prev, &SentinelSegment); 270 DCHECK_NE(S, &SentinelSegment); 271 Head = S; 272 Tail = S; 273 DCHECK_EQ(Head, Tail); 274 DCHECK_EQ(Tail->Next, &SentinelSegment); 275 DCHECK_EQ(Tail->Prev, &SentinelSegment); 276 return S; 277 } 278 279 Segment *AppendNewSegment() XRAY_NEVER_INSTRUMENT { 280 auto S = NewSegment(); 281 if (S == nullptr) 282 return nullptr; 283 DCHECK_NE(Tail, &SentinelSegment); 284 DCHECK_EQ(Tail->Next, &SentinelSegment); 285 DCHECK_EQ(S->Prev, &SentinelSegment); 286 DCHECK_EQ(S->Next, &SentinelSegment); 287 S->Prev = Tail; 288 Tail->Next = S; 289 Tail = S; 290 DCHECK_EQ(S, S->Prev->Next); 291 DCHECK_EQ(Tail->Next, &SentinelSegment); 292 return S; 293 } 294 295 public: 296 explicit Array(AllocatorType &A) XRAY_NEVER_INSTRUMENT 297 : Alloc(&A), 298 Head(&SentinelSegment), 299 Tail(&SentinelSegment), 300 Freelist(&SentinelSegment), 301 Size(0) {} 302 303 Array() XRAY_NEVER_INSTRUMENT : Alloc(nullptr), 304 Head(&SentinelSegment), 305 Tail(&SentinelSegment), 306 Freelist(&SentinelSegment), 307 Size(0) {} 308 309 Array(const Array &) = delete; 310 Array &operator=(const Array &) = delete; 311 312 Array(Array &&O) XRAY_NEVER_INSTRUMENT : Alloc(O.Alloc), 313 Head(O.Head), 314 Tail(O.Tail), 315 Freelist(O.Freelist), 316 Size(O.Size) { 317 O.Alloc = nullptr; 318 O.Head = &SentinelSegment; 319 O.Tail = &SentinelSegment; 320 O.Size = 0; 321 O.Freelist = &SentinelSegment; 322 } 323 324 Array &operator=(Array &&O) XRAY_NEVER_INSTRUMENT { 325 Alloc = O.Alloc; 326 O.Alloc = nullptr; 327 Head = O.Head; 328 O.Head = &SentinelSegment; 329 Tail = O.Tail; 330 O.Tail = &SentinelSegment; 331 Freelist = O.Freelist; 332 O.Freelist = &SentinelSegment; 333 Size = O.Size; 334 O.Size = 0; 335 return *this; 336 } 337 338 ~Array() XRAY_NEVER_INSTRUMENT { 339 for (auto &E : *this) 340 (&E)->~T(); 341 } 342 343 bool empty() const XRAY_NEVER_INSTRUMENT { return Size == 0; } 344 345 AllocatorType &allocator() const XRAY_NEVER_INSTRUMENT { 346 DCHECK_NE(Alloc, nullptr); 347 return *Alloc; 348 } 349 350 uint64_t size() const XRAY_NEVER_INSTRUMENT { return Size; } 351 352 template <class... Args> 353 T *AppendEmplace(Args &&... args) XRAY_NEVER_INSTRUMENT { 354 DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) || 355 (Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment)); 356 if (UNLIKELY(Head == &SentinelSegment)) { 357 auto R = InitHeadAndTail(); 358 if (R == nullptr) 359 return nullptr; 360 } 361 362 DCHECK_NE(Head, &SentinelSegment); 363 DCHECK_NE(Tail, &SentinelSegment); 364 365 auto Offset = Size % ElementsPerSegment; 366 if (UNLIKELY(Size != 0 && Offset == 0)) 367 if (AppendNewSegment() == nullptr) 368 return nullptr; 369 370 DCHECK_NE(Tail, &SentinelSegment); 371 auto Base = &Tail->Data; 372 auto AlignedOffset = Base + (Offset * AlignedElementStorageSize); 373 DCHECK_LE(AlignedOffset + sizeof(T), 374 reinterpret_cast<unsigned char *>(Base) + SegmentSize); 375 376 // In-place construct at Position. 377 new (AlignedOffset) T{std::forward<Args>(args)...}; 378 ++Size; 379 return reinterpret_cast<T *>(AlignedOffset); 380 } 381 382 T *Append(const T &E) XRAY_NEVER_INSTRUMENT { 383 // FIXME: This is a duplication of AppenEmplace with the copy semantics 384 // explicitly used, as a work-around to GCC 4.8 not invoking the copy 385 // constructor with the placement new with braced-init syntax. 386 DCHECK((Size == 0 && Head == &SentinelSegment && Head == Tail) || 387 (Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment)); 388 if (UNLIKELY(Head == &SentinelSegment)) { 389 auto R = InitHeadAndTail(); 390 if (R == nullptr) 391 return nullptr; 392 } 393 394 DCHECK_NE(Head, &SentinelSegment); 395 DCHECK_NE(Tail, &SentinelSegment); 396 397 auto Offset = Size % ElementsPerSegment; 398 if (UNLIKELY(Size != 0 && Offset == 0)) 399 if (AppendNewSegment() == nullptr) 400 return nullptr; 401 402 DCHECK_NE(Tail, &SentinelSegment); 403 auto Base = &Tail->Data; 404 auto AlignedOffset = Base + (Offset * AlignedElementStorageSize); 405 DCHECK_LE(AlignedOffset + sizeof(T), 406 reinterpret_cast<unsigned char *>(Tail) + SegmentSize); 407 408 // In-place construct at Position. 409 new (AlignedOffset) T(E); 410 ++Size; 411 return reinterpret_cast<T *>(AlignedOffset); 412 } 413 414 T &operator[](uint64_t Offset) const XRAY_NEVER_INSTRUMENT { 415 DCHECK_LE(Offset, Size); 416 // We need to traverse the array enough times to find the element at Offset. 417 auto S = Head; 418 while (Offset >= ElementsPerSegment) { 419 S = S->Next; 420 Offset -= ElementsPerSegment; 421 DCHECK_NE(S, &SentinelSegment); 422 } 423 auto Base = &S->Data; 424 auto AlignedOffset = Base + (Offset * AlignedElementStorageSize); 425 auto Position = reinterpret_cast<T *>(AlignedOffset); 426 return *reinterpret_cast<T *>(Position); 427 } 428 429 T &front() const XRAY_NEVER_INSTRUMENT { 430 DCHECK_NE(Head, &SentinelSegment); 431 DCHECK_NE(Size, 0u); 432 return *begin(); 433 } 434 435 T &back() const XRAY_NEVER_INSTRUMENT { 436 DCHECK_NE(Tail, &SentinelSegment); 437 DCHECK_NE(Size, 0u); 438 auto It = end(); 439 --It; 440 return *It; 441 } 442 443 template <class Predicate> 444 T *find_element(Predicate P) const XRAY_NEVER_INSTRUMENT { 445 if (empty()) 446 return nullptr; 447 448 auto E = end(); 449 for (auto I = begin(); I != E; ++I) 450 if (P(*I)) 451 return &(*I); 452 453 return nullptr; 454 } 455 456 /// Remove N Elements from the end. This leaves the blocks behind, and not 457 /// require allocation of new blocks for new elements added after trimming. 458 void trim(uint64_t Elements) XRAY_NEVER_INSTRUMENT { 459 auto OldSize = Size; 460 Elements = Elements > Size ? Size : Elements; 461 Size -= Elements; 462 463 // We compute the number of segments we're going to return from the tail by 464 // counting how many elements have been trimmed. Given the following: 465 // 466 // - Each segment has N valid positions, where N > 0 467 // - The previous size > current size 468 // 469 // To compute the number of segments to return, we need to perform the 470 // following calculations for the number of segments required given 'x' 471 // elements: 472 // 473 // f(x) = { 474 // x == 0 : 0 475 // , 0 < x <= N : 1 476 // , N < x <= max : x / N + (x % N ? 1 : 0) 477 // } 478 // 479 // We can simplify this down to: 480 // 481 // f(x) = { 482 // x == 0 : 0, 483 // , 0 < x <= max : x / N + (x < N || x % N ? 1 : 0) 484 // } 485 // 486 // And further down to: 487 // 488 // f(x) = x ? x / N + (x < N || x % N ? 1 : 0) : 0 489 // 490 // We can then perform the following calculation `s` which counts the number 491 // of segments we need to remove from the end of the data structure: 492 // 493 // s(p, c) = f(p) - f(c) 494 // 495 // If we treat p = previous size, and c = current size, and given the 496 // properties above, the possible range for s(...) is [0..max(typeof(p))/N] 497 // given that typeof(p) == typeof(c). 498 auto F = [](uint64_t X) { 499 return X ? (X / ElementsPerSegment) + 500 (X < ElementsPerSegment || X % ElementsPerSegment ? 1 : 0) 501 : 0; 502 }; 503 auto PS = F(OldSize); 504 auto CS = F(Size); 505 DCHECK_GE(PS, CS); 506 auto SegmentsToTrim = PS - CS; 507 for (auto I = 0uL; I < SegmentsToTrim; ++I) { 508 // Here we place the current tail segment to the freelist. To do this 509 // appropriately, we need to perform a splice operation on two 510 // bidirectional linked-lists. In particular, we have the current state of 511 // the doubly-linked list of segments: 512 // 513 // @S@ <- s0 <-> s1 <-> ... <-> sT -> @S@ 514 // 515 DCHECK_NE(Head, &SentinelSegment); 516 DCHECK_NE(Tail, &SentinelSegment); 517 DCHECK_EQ(Tail->Next, &SentinelSegment); 518 519 if (Freelist == &SentinelSegment) { 520 // Our two lists at this point are in this configuration: 521 // 522 // Freelist: (potentially) @S@ 523 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@ 524 // ^ Head ^ Tail 525 // 526 // The end state for us will be this configuration: 527 // 528 // Freelist: @S@<-sT->@S@ 529 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@ 530 // ^ Head ^ Tail 531 // 532 // The first step for us is to hold a reference to the tail of Mainlist, 533 // which in our notation is represented by sT. We call this our "free 534 // segment" which is the segment we are placing on the Freelist. 535 // 536 // sF = sT 537 // 538 // Then, we also hold a reference to the "pre-tail" element, which we 539 // call sPT: 540 // 541 // sPT = pred(sT) 542 // 543 // We want to splice sT into the beginning of the Freelist, which in 544 // an empty Freelist means placing a segment whose predecessor and 545 // successor is the sentinel segment. 546 // 547 // The splice operation then can be performed in the following 548 // algorithm: 549 // 550 // succ(sPT) = S 551 // pred(sT) = S 552 // succ(sT) = Freelist 553 // Freelist = sT 554 // Tail = sPT 555 // 556 auto SPT = Tail->Prev; 557 SPT->Next = &SentinelSegment; 558 Tail->Prev = &SentinelSegment; 559 Tail->Next = Freelist; 560 Freelist = Tail; 561 Tail = SPT; 562 563 // Our post-conditions here are: 564 DCHECK_EQ(Tail->Next, &SentinelSegment); 565 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 566 } else { 567 // In the other case, where the Freelist is not empty, we perform the 568 // following transformation instead: 569 // 570 // This transforms the current state: 571 // 572 // Freelist: @S@<-f0->@S@ 573 // ^ Freelist 574 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@ 575 // ^ Head ^ Tail 576 // 577 // Into the following: 578 // 579 // Freelist: @S@<-sT<->f0->@S@ 580 // ^ Freelist 581 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@ 582 // ^ Head ^ Tail 583 // 584 // The algorithm is: 585 // 586 // sFH = Freelist 587 // sPT = pred(sT) 588 // pred(SFH) = sT 589 // succ(sT) = Freelist 590 // pred(sT) = S 591 // succ(sPT) = S 592 // Tail = sPT 593 // Freelist = sT 594 // 595 auto SFH = Freelist; 596 auto SPT = Tail->Prev; 597 auto ST = Tail; 598 SFH->Prev = ST; 599 ST->Next = Freelist; 600 ST->Prev = &SentinelSegment; 601 SPT->Next = &SentinelSegment; 602 Tail = SPT; 603 Freelist = ST; 604 605 // Our post-conditions here are: 606 DCHECK_EQ(Tail->Next, &SentinelSegment); 607 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 608 DCHECK_EQ(Freelist->Next->Prev, Freelist); 609 } 610 } 611 612 // Now in case we've spliced all the segments in the end, we ensure that the 613 // main list is "empty", or both the head and tail pointing to the sentinel 614 // segment. 615 if (Tail == &SentinelSegment) 616 Head = Tail; 617 618 DCHECK( 619 (Size == 0 && Head == &SentinelSegment && Tail == &SentinelSegment) || 620 (Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment)); 621 DCHECK( 622 (Freelist != &SentinelSegment && Freelist->Prev == &SentinelSegment) || 623 (Freelist == &SentinelSegment && Tail->Next == &SentinelSegment)); 624 } 625 626 // Provide iterators. 627 Iterator<T> begin() const XRAY_NEVER_INSTRUMENT { 628 return Iterator<T>(Head, 0, Size); 629 } 630 Iterator<T> end() const XRAY_NEVER_INSTRUMENT { 631 return Iterator<T>(Tail, Size, Size); 632 } 633 Iterator<const T> cbegin() const XRAY_NEVER_INSTRUMENT { 634 return Iterator<const T>(Head, 0, Size); 635 } 636 Iterator<const T> cend() const XRAY_NEVER_INSTRUMENT { 637 return Iterator<const T>(Tail, Size, Size); 638 } 639 }; 640 641 // We need to have this storage definition out-of-line so that the compiler can 642 // ensure that storage for the SentinelSegment is defined and has a single 643 // address. 644 template <class T> 645 typename Array<T>::Segment Array<T>::SentinelSegment{ 646 &Array<T>::SentinelSegment, &Array<T>::SentinelSegment, {'\0'}}; 647 648 } // namespace __xray 649 650 #endif // XRAY_SEGMENTED_ARRAY_H 651