LLVM筆記(17) - ADT介紹(一) ilist


ADT(advanced data type)是LLVM針對其業務邏輯自定義的高性能抽象類型, 其定義見include/llvm/ADT目錄.
ADT目錄下既包含了DenseMap/SmallVector這類容器封裝, 也包含了APInt等大數運算支持, 還有triple這類從編譯器業務抽象出來的類型.
了解ADT可以幫助我們更高效的閱讀與修改編譯器代碼, 減輕開發工作量, 另一方面更好建立對編譯器的抽象認知. 本節將要介紹的是ilist, 一個LLVM自定義鏈表實現.

ilist簡介

ilist(intrusive list)是LLVM自定義的鏈表實現, 相比於std::list最主要的區別是其侵入式鏈表設計. 所謂侵入式(instrusive)設計是指鏈表節點本身作為其指向對象的一部分存在.
由於STL將容器與算法分離, 為兼容標准的迭代器設計, std::list采用單獨分配內存保存鏈表節點的方式, 鏈表節點本身與其指向對象相互獨立. 其優點是修改鏈表(insert或remove操作)時可以保持迭代器的合法性, 缺點是額外的內存開銷/較差的局部性.
LLVM提供的ilist是侵入式的雙向鏈表, 與STL鏈表類似它也提供常量時間的操作接口(insert/remove/splice), 使用該鏈表的數據類型通過繼承鏈表基類實現統一的生命周期管理, 因而具有更好的局部性與更高效的訪問效率等特點, 恰恰適用於LLVM某些場景.
關於更多的ilist使用背景介紹可以看官方文檔.

從實現角度來看, 實現intrusive list與non-intrusive list的區別在於:

  1. 鏈表節點基類只需提供操作接口, 無需設計容器.
  2. 額外的鏈表類封裝, 用於對外提供容器方式的訪問.
  3. 迭代器設計.

以下從這三點入手簡要窺探一下ilist的實現.

鏈表節點定義

對於instrusive list node而言只需定義其前驅與后驅指針即可, 使用時由具體數據類型繼承該基類即可, 類似以下代碼:

struct A {
  A *prev;
  A *next;
};

class B : A {
  // real data structure
};

LLVM的實現也不例外, ilist支持多種鏈表節點(ilist_node/ilist_node_with_parent), 它們均為ilist_node_base的封裝. 基類ilist_node_base(defined in include/llvm/ADT/ilist_node_base.h)定義見下.

template <bool EnableSentinelTracking> class ilist_node_base;

template <> class ilist_node_base<false> {
  ilist_node_base *Prev = nullptr;
  ilist_node_base *Next = nullptr;

public:
  void setPrev(ilist_node_base *Prev) { this->Prev = Prev; }
  void setNext(ilist_node_base *Next) { this->Next = Next; }
  ilist_node_base *getPrev() const { return Prev; }
  ilist_node_base *getNext() const { return Next; }

  bool isKnownSentinel() const { return false; }
  void initializeSentinel() {}
};

template <> class ilist_node_base<true> {
  PointerIntPair<ilist_node_base *, 1> PrevAndSentinel;
  ilist_node_base *Next = nullptr;

public:
  void setPrev(ilist_node_base *Prev) { PrevAndSentinel.setPointer(Prev); }
  void setNext(ilist_node_base *Next) { this->Next = Next; }
  ilist_node_base *getPrev() const { return PrevAndSentinel.getPointer(); }
  ilist_node_base *getNext() const { return Next; }

  bool isSentinel() const { return PrevAndSentinel.getInt(); }
  bool isKnownSentinel() const { return isSentinel(); }
  void initializeSentinel() { PrevAndSentinel.setInt(true); }
};

注意到ilist_node_base包含了前驅(Prev/PrevAndSentinel)與后驅(Next)指針, 以及實現了對應的set/get操作.
另外ilist_node_base的模板參數EnableSentinelTracking指明了是否使能邊界檢查, 其作用是使能ilist迭代器(ilist_iterator)對邊界的檢查(后文會提到).
如果該值為true則將前驅的LSB位作為標記指明當前鏈表節點是否為sentinel元素(即一個ilist_sentinel類的實例), 若為false則isSentinel()永遠返回false(即檢查一直pass).
通過打開LLVM_ENABLE_ABI_BREAKING_CHECKS選項使能EnableSentinelTracking, 這在調試鏈表問題時可以提供幫助.

回到ilist_node(defined in include/llvm/ADT/ilist_node.h), 其繼承自ilist_node_impl, 其主要作用是提供靜態類型檢查與提供迭代器接口.
ilist_node提供了私有的獲取前驅/后驅指針的方法(用於實現迭代器操作), 其本身通過迭代器訪問成員.

template <class OptionsT> class ilist_node_impl : OptionsT::node_base_type {
  using node_base_type = typename OptionsT::node_base_type;

  friend struct ilist_detail::NodeAccess;

protected:
  using self_iterator = ilist_iterator<OptionsT, false, false>;
  using const_self_iterator = ilist_iterator<OptionsT, false, true>;
  using reverse_self_iterator = ilist_iterator<OptionsT, true, false>;
  using const_reverse_self_iterator = ilist_iterator<OptionsT, true, true>;

  ilist_node_impl() = default;

private:
  ilist_node_impl *getPrev() {
    return static_cast<ilist_node_impl *>(node_base_type::getPrev());
  }

  ilist_node_impl *getNext() {
    return static_cast<ilist_node_impl *>(node_base_type::getNext());
  }

  const ilist_node_impl *getPrev() const {
    return static_cast<ilist_node_impl *>(node_base_type::getPrev());
  }

  const ilist_node_impl *getNext() const {
    return static_cast<ilist_node_impl *>(node_base_type::getNext());
  }

  void setPrev(ilist_node_impl *N) { node_base_type::setPrev(N); }
  void setNext(ilist_node_impl *N) { node_base_type::setNext(N); }

public:
  self_iterator getIterator() { return self_iterator(*this); }
  const_self_iterator getIterator() const { return const_self_iterator(*this); }

  reverse_self_iterator getReverseIterator() {
    return reverse_self_iterator(*this);
  }

  const_reverse_self_iterator getReverseIterator() const {
    return const_reverse_self_iterator(*this);
  }

  using node_base_type::isKnownSentinel;

  bool isSentinel() const {
    return node_base_type::isSentinel();
  }
};

template <class T, class... Options>
class ilist_node : public ilist_node_impl<typename ilist_detail::compute_node_options<T, Options...>::type> {
  static_assert(ilist_detail::check_options<Options...>::value, "Unrecognized node option!");
};

注意這里ilist_node_impl的模板參數OptionsT, 其接受的參數compute_node_options(defined in include/llvm/ADT/ilist_node_options.h)作用是特化模板.
因此ilist_node_impl中的node_base_type類型被推導為ilist_node_base<enable_sentinel_tracking>, 即ilist_node_impl繼承自ilist_node_base.

template <class T, bool EnableSentinelTracking, bool IsSentinelTrackingExplicit, class TagT>
struct node_options {
  typedef T value_type;
  typedef T *pointer;
  typedef T &reference;
  typedef const T *const_pointer;
  typedef const T &const_reference;

  static const bool enable_sentinel_tracking = EnableSentinelTracking;
  static const bool is_sentinel_tracking_explicit = IsSentinelTrackingExplicit;
  typedef TagT tag;
  typedef ilist_node_base<enable_sentinel_tracking> node_base_type;
  typedef ilist_base<enable_sentinel_tracking> list_base_type;
};

template <class T, class... Options> struct compute_node_options {
  typedef node_options<T, extract_sentinel_tracking<Options...>::value,
                       extract_sentinel_tracking<Options...>::is_explicit,
                       typename extract_tag<Options...>::type>
      type;
};

在LLVM中有存在許多相互依賴的數據結構: 比如函數與基本塊. 它們具有以下特點:

  1. 一個函數包含多個基本塊, 因此函數需要能夠具有迭代訪問基本塊的能力.
  2. 因為基本塊經常被修改, 需要高效的新增/刪除操作, 因此基本塊在物理排布上並不連續.
  3. 另一方面前一個基本塊可能越過后一個基本塊跳轉到第三個基本塊(通過有向圖實現), 因此額外實現基本塊本身迭代的場景較少.

對於這類情況LLVM提供了另一個特殊的鏈表節點ilist_node_with_parent, 其接受一個ParentTy參數作為(業務邏輯上的)父類節點. 鏈表節點之間通過訪問父類的數據結構獲取其下一個節點的指針.

template <typename NodeTy, typename ParentTy, class... Options>
class ilist_node_with_parent : public ilist_node<NodeTy, Options...> {
protected:
  ilist_node_with_parent() = default;

private:
  const ParentTy *getNodeParent() const {
    return static_cast<const NodeTy *>(this)->getParent();
  }

public:
  NodeTy *getPrevNode() {
    const auto &List =
        getNodeParent()->*(ParentTy::getSublistAccess((NodeTy *)nullptr));
    return List.getPrevNode(*static_cast<NodeTy *>(this));
  }

  const NodeTy *getPrevNode() const {
    return const_cast<ilist_node_with_parent *>(this)->getPrevNode();
  }

  NodeTy *getNextNode() {
    const auto &List =
        getNodeParent()->*(ParentTy::getSublistAccess((NodeTy *)nullptr));
    return List.getNextNode(*static_cast<NodeTy *>(this));
  }

  const NodeTy *getNextNode() const {
    return const_cast<ilist_node_with_parent *>(this)->getNextNode();
  }
};

由於(業務邏輯上的)子節點通過父節點訪問其相鄰的節點, 在使用此鏈表時需要實現以下接口:

  1. 子節點(NodeTy)需要實現getParent()方法獲取對應的父節點對象指針.
  2. 父節點(ParentTy)必須實現getSublistAccess()方法獲取子節點鏈表.

最后再看一個特殊的鏈表節點ilist_sentinel, 其作為鏈表的邊界元素存在.
注意其構造函數會先調用initializeSentinel()初始化sentinel標記, 在EnableSentinelTracking為true時會將標記位設true.

template <class OptionsT>
class ilist_sentinel : public ilist_node_impl<OptionsT> {
public:
  ilist_sentinel() {
    this->initializeSentinel();
    reset();
  }

  void reset() {
    this->setPrev(this);
    this->setNext(this);
  }

  bool empty() const { return this == this->getPrev(); }
};

鏈表容器封裝

有了ilist_node與ilist_node_with_parent即可使用鏈表, 但我們更希望以標准容器的方式去訪問它們.
LLVM也提供了容器封裝的鏈表ilist/iplist(defined in include/llvm/ADT/ilist.h), 其繼承自iplist_impl, 后者實現了與標准鏈表容器兼容的接口的封裝.

template <class IntrusiveListT, class TraitsT>
class iplist_impl : public TraitsT, IntrusiveListT {
  typedef IntrusiveListT base_list_type;

public:
  iplist_impl() = default;

  iplist_impl(const iplist_impl &) = delete;
  iplist_impl &operator=(const iplist_impl &) = delete;

  iplist_impl(iplist_impl &&X)
      : TraitsT(std::move(static_cast<TraitsT &>(X))),
        IntrusiveListT(std::move(static_cast<IntrusiveListT &>(X))) {}
  iplist_impl &operator=(iplist_impl &&X) {
    *static_cast<TraitsT *>(this) = std::move(static_cast<TraitsT &>(X));
    *static_cast<IntrusiveListT *>(this) =
        std::move(static_cast<IntrusiveListT &>(X));
    return *this;
  }

  ~iplist_impl() { clear(); }

  pointer remove(iterator &IT) {
    pointer Node = &*IT++;
    this->removeNodeFromList(Node); // Notify traits that we removed a node...
    base_list_type::remove(*Node);
    return Node;
  }

  // erase - remove a node from the controlled sequence... and delete it.
  iterator erase(iterator where) {
    this->deleteNode(remove(where));
    return where;
  }

  ......
};

template <class T, class... Options>
class iplist
    : public iplist_impl<simple_ilist<T, Options...>, ilist_traits<T>> {
  using iplist_impl_type = typename iplist::iplistiplist繼承自iplist_impl, 后者實現了與標准鏈表容器兼容的接口的封裝._impl;

public:
  iplist() = default;

  iplist(const iplist &X) = delete;
  iplist &operator=(const iplist &X) = delete;

  iplist(iplist &&X) : iplist_impl_type(std::move(X)) {}
  iplist &operator=(iplist &&X) {
    *static_cast<iplist_impl_type *>(this) = std::move(X);
    return *this;
  }
};

template <class T, class... Options> using ilist = iplist<T, Options...>;

iplist_impl提供了許多標准容器的接口, 這里就不一一介紹, 注意到它接受兩個模板參數TraitsT與IntrusiveListT, 前者接受一個萃取器用於控制所有權語義, 后者接受一個intrusive list作為底層實現.
先來看下萃取器ilist_traits(defined in include/llvm/ADT/ilist.h)如何決定所有權語義.
LLVM定義了兩種ilist_traits(ilist_alloc_traits/ilist_noalloc_traits), 前者在刪除節點時(注意到iplist_impl::erase()中調用了deleteNode())會析構對象V, 而后者是空實現.
注意iplist將TraitsT實例化為ilist_traits , 而ilist_traits繼承自ilist_alloc_traits, 即iplist鏈表控制節點的所有權.

template <typename NodeTy> struct ilist_alloc_traits {
  static void deleteNode(NodeTy *V) { delete V; }
};

template <typename NodeTy> struct ilist_noalloc_traits {
  static void deleteNode(NodeTy *V) {}
};

template <typename NodeTy> struct ilist_callback_traits {
  void addNodeToList(NodeTy *) {}
  void removeNodeFromList(NodeTy *) {}

  template <class Iterator>
  void transferNodesFromList(ilist_callback_traits &OldList, Iterator /*first*/,
                             Iterator /*last*/) {
    (void)OldList;
  }
};

template <typename NodeTy> struct ilist_node_traits : ilist_alloc_traits<NodeTy>, ilist_callback_traits<NodeTy> {};

template <typename NodeTy> struct ilist_traits : public ilist_node_traits<NodeTy> {};

template <typename Ty> struct ilist_traits<const Ty> {};

再來看下IntrusiveListT的底層實現simple_ilist(defined in include/llvm/ADT/simple_ilist.h), 正如名字所見simple_ilist實現了一個基礎的instrusive list, 它有幾個特點:

  1. simple_ilist繼承自ilist_node.
  2. simple_ilist不會分配/釋放所管理的對象, 插入/刪除節點也不會轉移對象所有權.
  3. 由於第2點, 插入節點的操作push_front()/push_back()/insert()只接受對象的引用而非指針.
  4. 類似的, 刪除節點的操作remove()/erase()/clear()也不會真正刪除對象, 而是僅將其移出鏈表.

simple_list唯一擁有的成員是一個鏈表頭節點(list_sentinel), 迭代器begin()從list_sentinel后驅開始, end()則指向list_sentinel本身.

template <typename T, class... Options>
class simple_ilist
    : ilist_detail::compute_node_options<T, Options...>::type::list_base_type,
      ilist_detail::SpecificNodeAccess<typename ilist_detail::compute_node_options<T, Options...>::type> {
  static_assert(ilist_detail::check_options<Options...>::value,
                "Unrecognized node option!");
  using OptionsT =
      typename ilist_detail::compute_node_options<T, Options...>::type;
  using list_base_type = typename OptionsT::list_base_type;
  ilist_sentinel<OptionsT> Sentinel;

public:
  simple_ilist() = default;
  ~simple_ilist() = default;

  // No copy constructors.
  simple_ilist(const simple_ilist &) = delete;
  simple_ilist &operator=(const simple_ilist &) = delete;

  // Move constructors.
  simple_ilist(simple_ilist &&X) { splice(end(), X); }
  simple_ilist &operator=(simple_ilist &&X) {
    clear();
    splice(end(), X);
    return *this;
  }

  iterator begin() { return ++iterator(Sentinel); }
  const_iterator begin() const { return ++const_iterator(Sentinel); }
  iterator end() { return iterator(Sentinel); }
  const_iterator end() const { return const_iterator(Sentinel); }
  reverse_iterator rbegin() { return ++reverse_iterator(Sentinel); }
  const_reverse_iterator rbegin() const {
    return ++const_reverse_iterator(Sentinel);
  }
  reverse_iterator rend() { return reverse_iterator(Sentinel); }
  const_reverse_iterator rend() const {
    return const_reverse_iterator(Sentinel);
  }

  /// Check if the list is empty in constant time.
  LLVM_NODISCARD bool empty() const { return Sentinel.empty(); }

  /// Calculate the size of the list in linear time.
  LLVM_NODISCARD size_type size() const {
    return std::distance(begin(), end());
  }

  reference front() { return *begin(); }
  const_reference front() const { return *begin(); }
  reference back() { return *rbegin(); }
  const_reference back() const { return *rbegin(); }

  /// Insert a node at the front; never copies.
  void push_front(reference Node) { insert(begin(), Node); }

  /// Insert a node at the back; never copies.
  void push_back(reference Node) { insert(end(), Node); }

  /// Remove the node at the front; never deletes.
  void pop_front() { erase(begin()); }

  /// Remove the node at the back; never deletes.
  void pop_back() { erase(--end()); }

  /// Swap with another list in place using std::swap.
  void swap(simple_ilist &X) { std::swap(*this, X); }

  /// Insert a node by reference; never copies.
  iterator insert(iterator I, reference Node) {
    list_base_type::insertBefore(*I.getNodePtr(), *this->getNodePtr(&Node));
    return iterator(&Node);
  }

  /// Insert a range of nodes; never copies.
  template <class Iterator>
  void insert(iterator I, Iterator First, Iterator Last) {
    for (; First != Last; ++First)
      insert(I, *First);
  }

  /// Clone another list.
  template <class Cloner, class Disposer>
  void cloneFrom(const simple_ilist &L2, Cloner clone, Disposer dispose) {
    clearAndDispose(dispose);
    for (const_reference V : L2)
      push_back(*clone(V));
  }

  /// Remove a node by reference; never deletes.
  ///
  /// \see \a erase() for removing by iterator.
  /// \see \a removeAndDispose() if the node should be deleted.
  void remove(reference N) { list_base_type::remove(*this->getNodePtr(&N)); }

  /// Remove a node by iterator; never deletes.
  ///
  /// \see \a remove() for removing by reference.
  /// \see \a eraseAndDispose() it the node should be deleted.
  iterator erase(iterator I) {
    assert(I != end() && "Cannot remove end of list!");
    remove(*I++);
    return I;
  }

  /// Remove a range of nodes; never deletes.
  ///
  /// \see \a eraseAndDispose() if the nodes should be deleted.
  iterator erase(iterator First, iterator Last) {
    list_base_type::removeRange(*First.getNodePtr(), *Last.getNodePtr());
    return Last;
  }

  /// Clear the list; never deletes.
  ///
  /// \see \a clearAndDispose() if the nodes should be deleted.
  void clear() { Sentinel.reset(); }

  ......
};

注意到以上代碼中出現了兩個工具類compute_node_options(上文提到過用於特化模板參數)與SpecificNodeAccess(defined in include/llvm/ADT/ilist_node.h), 后者作用是提供公有API訪問ilist_node的私有方法.

struct NodeAccess {
protected:
  template <class OptionsT>
  static ilist_node_impl<OptionsT> *getNodePtr(typename OptionsT::pointer N) {
    return N;
  }
  template <class OptionsT>
  static const ilist_node_impl<OptionsT> *
  getNodePtr(typename OptionsT::const_pointer N) {
    return N;
  }
  template <class OptionsT>
  static typename OptionsT::pointer getValuePtr(ilist_node_impl<OptionsT> *N) {
    return static_cast<typename OptionsT::pointer>(N);
  }
  template <class OptionsT>
  static typename OptionsT::const_pointer
  getValuePtr(const ilist_node_impl<OptionsT> *N) {
    return static_cast<typename OptionsT::const_pointer>(N);
  }
  template <class OptionsT>
  static ilist_node_impl<OptionsT> *getPrev(ilist_node_impl<OptionsT> &N) {
    return N.getPrev();
  }
  template <class OptionsT>
  static ilist_node_impl<OptionsT> *getNext(ilist_node_impl<OptionsT> &N) {
    return N.getNext();
  }
  template <class OptionsT>
  static const ilist_node_impl<OptionsT> *
  getPrev(const ilist_node_impl<OptionsT> &N) {
    return N.getPrev();
  }
  template <class OptionsT>
  static const ilist_node_impl<OptionsT> *
  getNext(const ilist_node_impl<OptionsT> &N) {
    return N.getNext();
  }
};

template <class OptionsT> struct SpecificNodeAccess : NodeAccess {
protected:
  using pointer = typename OptionsT::pointer;
  using const_pointer = typename OptionsT::const_pointer;
  using node_type = ilist_node_impl<OptionsT>;

  static node_type *getNodePtr(pointer N) {
    return NodeAccess::getNodePtr<OptionsT>(N);
  }
  static const node_type *getNodePtr(const_pointer N) {
    return NodeAccess::getNodePtr<OptionsT>(N);
  }
  static pointer getValuePtr(node_type *N) {
    return NodeAccess::getValuePtr<OptionsT>(N);
  }
  static const_pointer getValuePtr(const node_type *N) {
    return NodeAccess::getValuePtr<OptionsT>(N);
  }
};

迭代器支持

ilist允許用戶以以迭代器方式訪問數據, 其迭代器ilist_iterator(defined in include/llvm/ADT/ilist_iterator.h)見下.

template <class OptionsT, bool IsReverse, bool IsConst>
class ilist_iterator : ilist_detail::SpecificNodeAccess<OptionsT> {
private:
  node_pointer NodePtr = nullptr;

public:
  /// Create from an ilist_node.
  explicit ilist_iterator(node_reference N) : NodePtr(&N) {}
  explicit ilist_iterator(pointer NP) : NodePtr(Access::getNodePtr(NP)) {}
  explicit ilist_iterator(reference NR) : NodePtr(Access::getNodePtr(&NR)) {}
  ilist_iterator() = default;

  // Accessors...
  reference operator*() const {
    assert(!NodePtr->isKnownSentinel());
    return *Access::getValuePtr(NodePtr);
  }
  pointer operator->() const { return &operator*(); }

  // Comparison operators
  friend bool operator==(const ilist_iterator &LHS, const ilist_iterator &RHS) {
    return LHS.NodePtr == RHS.NodePtr;
  }
  friend bool operator!=(const ilist_iterator &LHS, const ilist_iterator &RHS) {
    return LHS.NodePtr != RHS.NodePtr;
  }

  // Increment and decrement operators...
  ilist_iterator &operator--() {
    NodePtr = IsReverse ? NodePtr->getNext() : NodePtr->getPrev();
    return *this;
  }
  ilist_iterator &operator++() {
    NodePtr = IsReverse ? NodePtr->getPrev() : NodePtr->getNext();
    return *this;
  }
  ilist_iterator operator--(int) {
    ilist_iterator tmp = *this;
    --*this;
    return tmp;
  }
  ilist_iterator operator++(int) {
    ilist_iterator tmp = *this;
    ++*this;
    return tmp;
  }

  /// Get the underlying ilist_node.
  node_pointer getNodePtr() const { return static_cast<node_pointer>(NodePtr); }
  /// Check for end.  Only valid if ilist_sentinel_tracking<true>.
  bool isEnd() const { return NodePtr ? NodePtr->isSentinel() : false; }
};

ilist_iterator接受對象/對象指針初始化成員NodePtr, 其自增/自減操作通過訪問NodePtr的前驅/后驅實現. 若開啟sentinel檢查, 對迭代器做解引用操作時會首先檢查迭代器的合法性.

小結

ilist具有更好的內存局部性, 同時訪問效率更高, 因此是LLVM代碼中常見的數據結構. 在使用上需要注意的是:

  1. 鏈表節點可以選擇ilist_node或ilist_node_with_parent(亦或基於ilist_node_base擴展), 具體選擇哪種結構視業務邏輯決定.
  2. 在使用ilist_node_with_parent時需要注意實現getParent()(獲取父節點指針)與getSublistAccess()(獲取子節點鏈表)方法.
  3. 鏈表容器可以選擇simple_list或ilist, 注意其主要區別是是否擁有對象的所有權, 前者在刪除節點時需要手動釋放對象, 后者則會自動析構.
  4. 開啟編譯選項EnableSentinelTracking可以幫助調試鏈表問題.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM