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的區別在於:
- 鏈表節點基類只需提供操作接口, 無需設計容器.
- 額外的鏈表類封裝, 用於對外提供容器方式的訪問.
- 迭代器設計.
以下從這三點入手簡要窺探一下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中有存在許多相互依賴的數據結構: 比如函數與基本塊. 它們具有以下特點:
- 一個函數包含多個基本塊, 因此函數需要能夠具有迭代訪問基本塊的能力.
- 因為基本塊經常被修改, 需要高效的新增/刪除操作, 因此基本塊在物理排布上並不連續.
- 另一方面前一個基本塊可能越過后一個基本塊跳轉到第三個基本塊(通過有向圖實現), 因此額外實現基本塊本身迭代的場景較少.
對於這類情況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();
}
};
由於(業務邏輯上的)子節點通過父節點訪問其相鄰的節點, 在使用此鏈表時需要實現以下接口:
- 子節點(NodeTy)需要實現getParent()方法獲取對應的父節點對象指針.
- 父節點(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
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, 它有幾個特點:
- simple_ilist繼承自ilist_node.
- simple_ilist不會分配/釋放所管理的對象, 插入/刪除節點也不會轉移對象所有權.
- 由於第2點, 插入節點的操作push_front()/push_back()/insert()只接受對象的引用而非指針.
- 類似的, 刪除節點的操作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代碼中常見的數據結構. 在使用上需要注意的是:
- 鏈表節點可以選擇ilist_node或ilist_node_with_parent(亦或基於ilist_node_base擴展), 具體選擇哪種結構視業務邏輯決定.
- 在使用ilist_node_with_parent時需要注意實現getParent()(獲取父節點指針)與getSublistAccess()(獲取子節點鏈表)方法.
- 鏈表容器可以選擇simple_list或ilist, 注意其主要區別是是否擁有對象的所有權, 前者在刪除節點時需要手動釋放對象, 后者則會自動析構.
- 開啟編譯選項EnableSentinelTracking可以幫助調試鏈表問題.
