| //==--- ImmutableList.h - Immutable (functional) list interface --*- C++ -*-==// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file defines the ImmutableList class. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_ADT_IMMUTABLELIST_H |
| #define LLVM_ADT_IMMUTABLELIST_H |
| |
| #include "llvm/ADT/FoldingSet.h" |
| #include "llvm/Support/Allocator.h" |
| #include <cassert> |
| #include <cstdint> |
| #include <new> |
| |
| namespace llvm { |
| |
| template <typename T> class ImmutableListFactory; |
| |
| template <typename T> |
| class ImmutableListImpl : public FoldingSetNode { |
| friend class ImmutableListFactory<T>; |
| |
| T Head; |
| const ImmutableListImpl* Tail; |
| |
| template <typename ElemT> |
| ImmutableListImpl(ElemT &&head, const ImmutableListImpl *tail = nullptr) |
| : Head(std::forward<ElemT>(head)), Tail(tail) {} |
| |
| public: |
| ImmutableListImpl(const ImmutableListImpl &) = delete; |
| ImmutableListImpl &operator=(const ImmutableListImpl &) = delete; |
| |
| const T& getHead() const { return Head; } |
| const ImmutableListImpl* getTail() const { return Tail; } |
| |
| static inline void Profile(FoldingSetNodeID& ID, const T& H, |
| const ImmutableListImpl* L){ |
| ID.AddPointer(L); |
| ID.Add(H); |
| } |
| |
| void Profile(FoldingSetNodeID& ID) { |
| Profile(ID, Head, Tail); |
| } |
| }; |
| |
| /// ImmutableList - This class represents an immutable (functional) list. |
| /// It is implemented as a smart pointer (wraps ImmutableListImpl), so it |
| /// it is intended to always be copied by value as if it were a pointer. |
| /// This interface matches ImmutableSet and ImmutableMap. ImmutableList |
| /// objects should almost never be created directly, and instead should |
| /// be created by ImmutableListFactory objects that manage the lifetime |
| /// of a group of lists. When the factory object is reclaimed, all lists |
| /// created by that factory are released as well. |
| template <typename T> |
| class ImmutableList { |
| public: |
| using value_type = T; |
| using Factory = ImmutableListFactory<T>; |
| |
| static_assert(std::is_trivially_destructible<T>::value, |
| "T must be trivially destructible!"); |
| |
| private: |
| const ImmutableListImpl<T>* X; |
| |
| public: |
| // This constructor should normally only be called by ImmutableListFactory<T>. |
| // There may be cases, however, when one needs to extract the internal pointer |
| // and reconstruct a list object from that pointer. |
| ImmutableList(const ImmutableListImpl<T>* x = nullptr) : X(x) {} |
| |
| const ImmutableListImpl<T>* getInternalPointer() const { |
| return X; |
| } |
| |
| class iterator { |
| const ImmutableListImpl<T>* L = nullptr; |
| |
| public: |
| iterator() = default; |
| iterator(ImmutableList l) : L(l.getInternalPointer()) {} |
| |
| iterator& operator++() { L = L->getTail(); return *this; } |
| bool operator==(const iterator& I) const { return L == I.L; } |
| bool operator!=(const iterator& I) const { return L != I.L; } |
| const value_type& operator*() const { return L->getHead(); } |
| const std::remove_reference_t<value_type> *operator->() const { |
| return &L->getHead(); |
| } |
| |
| ImmutableList getList() const { return L; } |
| }; |
| |
| /// begin - Returns an iterator referring to the head of the list, or |
| /// an iterator denoting the end of the list if the list is empty. |
| iterator begin() const { return iterator(X); } |
| |
| /// end - Returns an iterator denoting the end of the list. This iterator |
| /// does not refer to a valid list element. |
| iterator end() const { return iterator(); } |
| |
| /// isEmpty - Returns true if the list is empty. |
| bool isEmpty() const { return !X; } |
| |
| bool contains(const T& V) const { |
| for (iterator I = begin(), E = end(); I != E; ++I) { |
| if (*I == V) |
| return true; |
| } |
| return false; |
| } |
| |
| /// isEqual - Returns true if two lists are equal. Because all lists created |
| /// from the same ImmutableListFactory are uniqued, this has O(1) complexity |
| /// because it the contents of the list do not need to be compared. Note |
| /// that you should only compare two lists created from the same |
| /// ImmutableListFactory. |
| bool isEqual(const ImmutableList& L) const { return X == L.X; } |
| |
| bool operator==(const ImmutableList& L) const { return isEqual(L); } |
| |
| /// getHead - Returns the head of the list. |
| const T& getHead() const { |
| assert(!isEmpty() && "Cannot get the head of an empty list."); |
| return X->getHead(); |
| } |
| |
| /// getTail - Returns the tail of the list, which is another (possibly empty) |
| /// ImmutableList. |
| ImmutableList getTail() const { |
| return X ? X->getTail() : nullptr; |
| } |
| |
| void Profile(FoldingSetNodeID& ID) const { |
| ID.AddPointer(X); |
| } |
| }; |
| |
| template <typename T> |
| class ImmutableListFactory { |
| using ListTy = ImmutableListImpl<T>; |
| using CacheTy = FoldingSet<ListTy>; |
| |
| CacheTy Cache; |
| uintptr_t Allocator; |
| |
| bool ownsAllocator() const { |
| return (Allocator & 0x1) == 0; |
| } |
| |
| BumpPtrAllocator& getAllocator() const { |
| return *reinterpret_cast<BumpPtrAllocator*>(Allocator & ~0x1); |
| } |
| |
| public: |
| ImmutableListFactory() |
| : Allocator(reinterpret_cast<uintptr_t>(new BumpPtrAllocator())) {} |
| |
| ImmutableListFactory(BumpPtrAllocator& Alloc) |
| : Allocator(reinterpret_cast<uintptr_t>(&Alloc) | 0x1) {} |
| |
| ~ImmutableListFactory() { |
| if (ownsAllocator()) delete &getAllocator(); |
| } |
| |
| template <typename ElemT> |
| [[nodiscard]] ImmutableList<T> concat(ElemT &&Head, ImmutableList<T> Tail) { |
| // Profile the new list to see if it already exists in our cache. |
| FoldingSetNodeID ID; |
| void* InsertPos; |
| |
| const ListTy* TailImpl = Tail.getInternalPointer(); |
| ListTy::Profile(ID, Head, TailImpl); |
| ListTy* L = Cache.FindNodeOrInsertPos(ID, InsertPos); |
| |
| if (!L) { |
| // The list does not exist in our cache. Create it. |
| BumpPtrAllocator& A = getAllocator(); |
| L = (ListTy*) A.Allocate<ListTy>(); |
| new (L) ListTy(std::forward<ElemT>(Head), TailImpl); |
| |
| // Insert the new list into the cache. |
| Cache.InsertNode(L, InsertPos); |
| } |
| |
| return L; |
| } |
| |
| template <typename ElemT> |
| [[nodiscard]] ImmutableList<T> add(ElemT &&Data, ImmutableList<T> L) { |
| return concat(std::forward<ElemT>(Data), L); |
| } |
| |
| template <typename... CtorArgs> |
| [[nodiscard]] ImmutableList<T> emplace(ImmutableList<T> Tail, |
| CtorArgs &&...Args) { |
| return concat(T(std::forward<CtorArgs>(Args)...), Tail); |
| } |
| |
| ImmutableList<T> getEmptyList() const { |
| return ImmutableList<T>(nullptr); |
| } |
| |
| template <typename ElemT> |
| ImmutableList<T> create(ElemT &&Data) { |
| return concat(std::forward<ElemT>(Data), getEmptyList()); |
| } |
| }; |
| |
| //===----------------------------------------------------------------------===// |
| // Partially-specialized Traits. |
| //===----------------------------------------------------------------------===// |
| |
| template <typename T> struct DenseMapInfo<ImmutableList<T>, void> { |
| static inline ImmutableList<T> getEmptyKey() { |
| return reinterpret_cast<ImmutableListImpl<T>*>(-1); |
| } |
| |
| static inline ImmutableList<T> getTombstoneKey() { |
| return reinterpret_cast<ImmutableListImpl<T>*>(-2); |
| } |
| |
| static unsigned getHashValue(ImmutableList<T> X) { |
| uintptr_t PtrVal = reinterpret_cast<uintptr_t>(X.getInternalPointer()); |
| return (unsigned((uintptr_t)PtrVal) >> 4) ^ |
| (unsigned((uintptr_t)PtrVal) >> 9); |
| } |
| |
| static bool isEqual(ImmutableList<T> X1, ImmutableList<T> X2) { |
| return X1 == X2; |
| } |
| }; |
| |
| } // end namespace llvm |
| |
| #endif // LLVM_ADT_IMMUTABLELIST_H |