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
25namespace __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.
33template <class T> class Array {
34  struct Segment {
35    Segment *Prev;
36    Segment *Next;
37    char Data[1];
38  };
39
40public:
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
79private:
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
295public:
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.
644template <class T>
645typename 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