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 = sizeof(T); 60 61 static constexpr uint64_t SegmentControlBlockSize = sizeof(Segment *) * 2; 62 63 static constexpr uint64_t SegmentSize = nearest_boundary( 64 SegmentControlBlockSize + next_pow2(sizeof(T)), kCacheLineSize); 65 66 using AllocatorType = Allocator<SegmentSize>; 67 68 static constexpr uint64_t ElementsPerSegment = 69 (SegmentSize - SegmentControlBlockSize) / next_pow2(sizeof(T)); 70 71 static_assert(ElementsPerSegment > 0, 72 "Must have at least 1 element per segment."); 73 74 static Segment SentinelSegment; 75 76 using size_type = uint64_t; 77 78 private: 79 // This Iterator models a BidirectionalIterator. 80 template <class U> class Iterator { 81 Segment *S = &SentinelSegment; 82 uint64_t Offset = 0; 83 uint64_t Size = 0; 84 85 public: 86 Iterator(Segment *IS, uint64_t Off, uint64_t S) XRAY_NEVER_INSTRUMENT 87 : S(IS), 88 Offset(Off), 89 Size(S) {} 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; 96 97 Iterator &operator++() XRAY_NEVER_INSTRUMENT { 98 if (++Offset % ElementsPerSegment || Offset == Size) 99 return *this; 100 101 // At this point, we know that Offset % N == 0, so we must advance the 102 // segment pointer. 103 DCHECK_EQ(Offset % ElementsPerSegment, 0); 104 DCHECK_NE(Offset, Size); 105 DCHECK_NE(S, &SentinelSegment); 106 DCHECK_NE(S->Next, &SentinelSegment); 107 S = S->Next; 108 DCHECK_NE(S, &SentinelSegment); 109 return *this; 110 } 111 112 Iterator &operator--() XRAY_NEVER_INSTRUMENT { 113 DCHECK_NE(S, &SentinelSegment); 114 DCHECK_GT(Offset, 0); 115 116 auto PreviousOffset = Offset--; 117 if (PreviousOffset != Size && PreviousOffset % ElementsPerSegment == 0) { 118 DCHECK_NE(S->Prev, &SentinelSegment); 119 S = S->Prev; 120 } 121 122 return *this; 123 } 124 125 Iterator operator++(int) XRAY_NEVER_INSTRUMENT { 126 Iterator Copy(*this); 127 ++(*this); 128 return Copy; 129 } 130 131 Iterator operator--(int) XRAY_NEVER_INSTRUMENT { 132 Iterator Copy(*this); 133 --(*this); 134 return Copy; 135 } 136 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; 141 } 142 143 template <class V, class W> 144 friend bool operator!=(const Iterator<V> &L, 145 const Iterator<W> &R) XRAY_NEVER_INSTRUMENT { 146 return !(L == R); 147 } 148 149 U &operator*() const XRAY_NEVER_INSTRUMENT { 150 DCHECK_NE(S, &SentinelSegment); 151 auto RelOff = Offset % ElementsPerSegment; 152 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); 158 } 159 160 U *operator->() const XRAY_NEVER_INSTRUMENT { return &(**this); } 161 }; 162 163 AllocatorType *Alloc; 164 Segment *Head; 165 Segment *Tail; 166 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. 169 Segment *Freelist; 170 uint64_t Size; 171 172 // =============================== 173 // In the following implementation, we work through the algorithms and the 174 // list operations using the following notation: 175 // 176 // - pred(s) is the predecessor (previous node accessor) and succ(s) is 177 // the successor (next node accessor). 178 // 179 // - S is a sentinel segment, which has the following property: 180 // 181 // pred(S) == succ(S) == S 182 // 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. 185 // 186 // - sL <-> sR : means a bidirectional relation between sL and sR, which 187 // means: 188 // 189 // succ(sL) == sR && pred(SR) == sL 190 // 191 // - sL -> sR : implies a unidirectional relation between sL and SR, 192 // with the following properties: 193 // 194 // succ(sL) == sR 195 // 196 // sL <- sR : implies a unidirectional relation between sR and sL, 197 // with the following properties: 198 // 199 // pred(sR) == sL 200 // 201 // =============================== 202 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: 210 // 211 // Freelist: @S@<-f0->...<->fN->@S@ 212 // ^ Freelist 213 // 214 // We want to perform a splice of `f0` from Freelist to a temporary list, 215 // which looks like: 216 // 217 // Templist: @S@<-f0->@S@ 218 // ^ FreeSegment 219 // 220 // Our algorithm preconditions are: 221 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 222 223 // Then the algorithm we implement is: 224 // 225 // SFS = Freelist 226 // Freelist = succ(Freelist) 227 // if (Freelist != S) 228 // pred(Freelist) = S 229 // succ(SFS) = S 230 // pred(SFS) = S 231 // 232 auto *FreeSegment = Freelist; 233 Freelist = Freelist->Next; 234 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; 241 242 FreeSegment->Next = &SentinelSegment; 243 FreeSegment->Prev = &SentinelSegment; 244 245 // Our postconditions are: 246 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 247 DCHECK_NE(FreeSegment, &SentinelSegment); 248 return FreeSegment; 249 } 250 251 auto SegmentBlock = Alloc->Allocate(); 252 if (SegmentBlock.Data == nullptr) 253 return nullptr; 254 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); 258 return SB; 259 } 260 261 Segment *InitHeadAndTail() XRAY_NEVER_INSTRUMENT { 262 DCHECK_EQ(Head, &SentinelSegment); 263 DCHECK_EQ(Tail, &SentinelSegment); 264 auto S = NewSegment(); 265 if (S == nullptr) 266 return nullptr; 267 DCHECK_EQ(S->Next, &SentinelSegment); 268 DCHECK_EQ(S->Prev, &SentinelSegment); 269 DCHECK_NE(S, &SentinelSegment); 270 Head = S; 271 Tail = S; 272 DCHECK_EQ(Head, Tail); 273 DCHECK_EQ(Tail->Next, &SentinelSegment); 274 DCHECK_EQ(Tail->Prev, &SentinelSegment); 275 return S; 276 } 277 278 Segment *AppendNewSegment() XRAY_NEVER_INSTRUMENT { 279 auto S = NewSegment(); 280 if (S == nullptr) 281 return nullptr; 282 DCHECK_NE(Tail, &SentinelSegment); 283 DCHECK_EQ(Tail->Next, &SentinelSegment); 284 DCHECK_EQ(S->Prev, &SentinelSegment); 285 DCHECK_EQ(S->Next, &SentinelSegment); 286 S->Prev = Tail; 287 Tail->Next = S; 288 Tail = S; 289 DCHECK_EQ(S, S->Prev->Next); 290 DCHECK_EQ(Tail->Next, &SentinelSegment); 291 return S; 292 } 293 294 public: 295 explicit Array(AllocatorType &A) XRAY_NEVER_INSTRUMENT 296 : Alloc(&A), 297 Head(&SentinelSegment), 298 Tail(&SentinelSegment), 299 Freelist(&SentinelSegment), 300 Size(0) {} 301 302 Array() XRAY_NEVER_INSTRUMENT : Alloc(nullptr), 303 Head(&SentinelSegment), 304 Tail(&SentinelSegment), 305 Freelist(&SentinelSegment), 306 Size(0) {} 307 308 Array(const Array &) = delete; 309 Array &operator=(const Array &) = delete; 310 311 Array(Array &&O) XRAY_NEVER_INSTRUMENT : Alloc(O.Alloc), 312 Head(O.Head), 313 Tail(O.Tail), 314 Freelist(O.Freelist), 315 Size(O.Size) { 316 O.Alloc = nullptr; 317 O.Head = &SentinelSegment; 318 O.Tail = &SentinelSegment; 319 O.Size = 0; 320 O.Freelist = &SentinelSegment; 321 } 322 323 Array &operator=(Array &&O) XRAY_NEVER_INSTRUMENT { 324 Alloc = O.Alloc; 325 O.Alloc = nullptr; 326 Head = O.Head; 327 O.Head = &SentinelSegment; 328 Tail = O.Tail; 329 O.Tail = &SentinelSegment; 330 Freelist = O.Freelist; 331 O.Freelist = &SentinelSegment; 332 Size = O.Size; 333 O.Size = 0; 334 return *this; 335 } 336 337 ~Array() XRAY_NEVER_INSTRUMENT { 338 for (auto &E : *this) 339 (&E)->~T(); 340 } 341 342 bool empty() const XRAY_NEVER_INSTRUMENT { return Size == 0; } 343 344 AllocatorType &allocator() const XRAY_NEVER_INSTRUMENT { 345 DCHECK_NE(Alloc, nullptr); 346 return *Alloc; 347 } 348 349 uint64_t size() const XRAY_NEVER_INSTRUMENT { return Size; } 350 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(); 357 if (R == nullptr) 358 return nullptr; 359 } 360 361 DCHECK_NE(Head, &SentinelSegment); 362 DCHECK_NE(Tail, &SentinelSegment); 363 364 auto Offset = Size % ElementsPerSegment; 365 if (UNLIKELY(Size != 0 && Offset == 0)) 366 if (AppendNewSegment() == nullptr) 367 return nullptr; 368 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); 374 375 // In-place construct at Position. 376 new (AlignedOffset) T{std::forward<Args>(args)...}; 377 ++Size; 378 return reinterpret_cast<T *>(AlignedOffset); 379 } 380 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(); 389 if (R == nullptr) 390 return nullptr; 391 } 392 393 DCHECK_NE(Head, &SentinelSegment); 394 DCHECK_NE(Tail, &SentinelSegment); 395 396 auto Offset = Size % ElementsPerSegment; 397 if (UNLIKELY(Size != 0 && Offset == 0)) 398 if (AppendNewSegment() == nullptr) 399 return nullptr; 400 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); 406 407 // In-place construct at Position. 408 new (AlignedOffset) T(E); 409 ++Size; 410 return reinterpret_cast<T *>(AlignedOffset); 411 } 412 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. 416 auto S = Head; 417 while (Offset >= ElementsPerSegment) { 418 S = S->Next; 419 Offset -= ElementsPerSegment; 420 DCHECK_NE(S, &SentinelSegment); 421 } 422 auto Base = &S->Data; 423 auto AlignedOffset = Base + (Offset * AlignedElementStorageSize); 424 auto Position = reinterpret_cast<T *>(AlignedOffset); 425 return *reinterpret_cast<T *>(Position); 426 } 427 428 T &front() const XRAY_NEVER_INSTRUMENT { 429 DCHECK_NE(Head, &SentinelSegment); 430 DCHECK_NE(Size, 0u); 431 return *begin(); 432 } 433 434 T &back() const XRAY_NEVER_INSTRUMENT { 435 DCHECK_NE(Tail, &SentinelSegment); 436 DCHECK_NE(Size, 0u); 437 auto It = end(); 438 --It; 439 return *It; 440 } 441 442 template <class Predicate> 443 T *find_element(Predicate P) const XRAY_NEVER_INSTRUMENT { 444 if (empty()) 445 return nullptr; 446 447 auto E = end(); 448 for (auto I = begin(); I != E; ++I) 449 if (P(*I)) 450 return &(*I); 451 452 return nullptr; 453 } 454 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 { 458 auto OldSize = Size; 459 Elements = Elements > Size ? Size : Elements; 460 Size -= Elements; 461 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: 464 // 465 // - Each segment has N valid positions, where N > 0 466 // - The previous size > current size 467 // 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' 470 // elements: 471 // 472 // f(x) = { 473 // x == 0 : 0 474 // , 0 < x <= N : 1 475 // , N < x <= max : x / N + (x % N ? 1 : 0) 476 // } 477 // 478 // We can simplify this down to: 479 // 480 // f(x) = { 481 // x == 0 : 0, 482 // , 0 < x <= max : x / N + (x < N || x % N ? 1 : 0) 483 // } 484 // 485 // And further down to: 486 // 487 // f(x) = x ? x / N + (x < N || x % N ? 1 : 0) : 0 488 // 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: 491 // 492 // s(p, c) = f(p) - f(c) 493 // 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) 500 : 0; 501 }; 502 auto PS = F(OldSize); 503 auto CS = F(Size); 504 DCHECK_GE(PS, CS); 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: 511 // 512 // @S@ <- s0 <-> s1 <-> ... <-> sT -> @S@ 513 // 514 DCHECK_NE(Head, &SentinelSegment); 515 DCHECK_NE(Tail, &SentinelSegment); 516 DCHECK_EQ(Tail->Next, &SentinelSegment); 517 518 if (Freelist == &SentinelSegment) { 519 // Our two lists at this point are in this configuration: 520 // 521 // Freelist: (potentially) @S@ 522 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@ 523 // ^ Head ^ Tail 524 // 525 // The end state for us will be this configuration: 526 // 527 // Freelist: @S@<-sT->@S@ 528 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@ 529 // ^ Head ^ Tail 530 // 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. 534 // 535 // sF = sT 536 // 537 // Then, we also hold a reference to the "pre-tail" element, which we 538 // call sPT: 539 // 540 // sPT = pred(sT) 541 // 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. 545 // 546 // The splice operation then can be performed in the following 547 // algorithm: 548 // 549 // succ(sPT) = S 550 // pred(sT) = S 551 // succ(sT) = Freelist 552 // Freelist = sT 553 // Tail = sPT 554 // 555 auto SPT = Tail->Prev; 556 SPT->Next = &SentinelSegment; 557 Tail->Prev = &SentinelSegment; 558 Tail->Next = Freelist; 559 Freelist = Tail; 560 Tail = SPT; 561 562 // Our post-conditions here are: 563 DCHECK_EQ(Tail->Next, &SentinelSegment); 564 DCHECK_EQ(Freelist->Prev, &SentinelSegment); 565 } else { 566 // In the other case, where the Freelist is not empty, we perform the 567 // following transformation instead: 568 // 569 // This transforms the current state: 570 // 571 // Freelist: @S@<-f0->@S@ 572 // ^ Freelist 573 // Mainlist: @S@<-s0<->s1<->...<->sPT<->sT->@S@ 574 // ^ Head ^ Tail 575 // 576 // Into the following: 577 // 578 // Freelist: @S@<-sT<->f0->@S@ 579 // ^ Freelist 580 // Mainlist: @S@<-s0<->s1<->...<->sPT->@S@ 581 // ^ Head ^ Tail 582 // 583 // The algorithm is: 584 // 585 // sFH = Freelist 586 // sPT = pred(sT) 587 // pred(SFH) = sT 588 // succ(sT) = Freelist 589 // pred(sT) = S 590 // succ(sPT) = S 591 // Tail = sPT 592 // Freelist = sT 593 // 594 auto SFH = Freelist; 595 auto SPT = Tail->Prev; 596 auto ST = Tail; 597 SFH->Prev = ST; 598 ST->Next = Freelist; 599 ST->Prev = &SentinelSegment; 600 SPT->Next = &SentinelSegment; 601 Tail = SPT; 602 Freelist = ST; 603 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); 608 } 609 } 610 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 613 // segment. 614 if (Tail == &SentinelSegment) 615 Head = Tail; 616 617 DCHECK( 618 (Size == 0 && Head == &SentinelSegment && Tail == &SentinelSegment) || 619 (Size != 0 && Head != &SentinelSegment && Tail != &SentinelSegment)); 620 DCHECK( 621 (Freelist != &SentinelSegment && Freelist->Prev == &SentinelSegment) || 622 (Freelist == &SentinelSegment && Tail->Next == &SentinelSegment)); 623 } 624 625 // Provide iterators. 626 Iterator<T> begin() const XRAY_NEVER_INSTRUMENT { 627 return Iterator<T>(Head, 0, Size); 628 } 629 Iterator<T> end() const XRAY_NEVER_INSTRUMENT { 630 return Iterator<T>(Tail, Size, Size); 631 } 632 Iterator<const T> cbegin() const XRAY_NEVER_INSTRUMENT { 633 return Iterator<const T>(Head, 0, Size); 634 } 635 Iterator<const T> cend() const XRAY_NEVER_INSTRUMENT { 636 return Iterator<const T>(Tail, Size, Size); 637 } 638 }; 639 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 642 // address. 643 template <class T> 644 typename Array<T>::Segment Array<T>::SentinelSegment{ 645 &Array<T>::SentinelSegment, &Array<T>::SentinelSegment, {'\0'}}; 646 647 } // namespace __xray 648 649 #endif // XRAY_SEGMENTED_ARRAY_H 650