xref: /freebsd/contrib/llvm-project/compiler-rt/lib/xray/xray_segmented_array.h (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
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