| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| /** |
| * @file A (circular) doubly linked list, abstracted over cons cell |
| * representations. |
| * |
| * See `tests/list-test.cc` for additional information on how to use it. |
| */ |
| |
| #pragma once |
| |
| #include <concepts> |
| #include <ds/pointer.h> |
| |
| namespace ds::linked_list |
| { |
| |
| namespace cell |
| { |
| /** |
| * The primitive, required, abstract interface to our cons cells. |
| * |
| * All methods are "namespaced" with `cell_` to support the case where |
| * the encoded forms are also representing other state (for example, |
| * bit-packed flags in pointer address bits). |
| */ |
| template<typename T> |
| concept HasCellOperations = requires(T &t) |
| { |
| /** Proxies for list linkages */ |
| { |
| t.cell_next() |
| } -> ds::pointer::proxy::Proxies<T>; |
| { |
| t.cell_prev() |
| } -> ds::pointer::proxy::Proxies<T>; |
| }; |
| |
| /** |
| * Reset a cell to a singleton ring. Not all cons cells are required to |
| * be able to do this, though if you're sticking to rings and not |
| * (ab)using the machinery here in interesting ways, this should be easy |
| * to specify. |
| * |
| * This is a method, and not a constructor, to handle cases where the |
| * cell is also packing other state into its representation. |
| */ |
| template<typename T> |
| concept HasReset = requires(T &t) |
| { |
| { |
| t.cell_reset() |
| } -> std::same_as<void>; |
| }; |
| |
| template<typename T> |
| concept HasCellOperationsReset = HasCellOperations<T> && HasReset<T>; |
| |
| /** |
| * Additional, optional overrides available within implementation of |
| * cons cells. It may be useful to static_assert() these in |
| * implementations to make sure we are not falling back to the defaults |
| * in terms of the above primops. |
| * |
| * @{ |
| */ |
| template<typename T> |
| concept HasIsSingleton = requires(T &t) |
| { |
| { |
| t.cell_is_singleton() |
| } -> std::same_as<bool>; |
| }; |
| |
| template<typename T> |
| concept HasIsSingletonCheck = requires(T &t) |
| { |
| { |
| t.cell_is_singleton_check() |
| } -> std::same_as<bool>; |
| }; |
| |
| template<typename T> |
| concept HasIsDoubleton = requires(T &t) |
| { |
| { |
| t.cell_is_doubleton() |
| } -> std::same_as<bool>; |
| }; |
| |
| /** @} */ |
| |
| } // namespace cell |
| |
| /** |
| * Self-loops indicate either the sentinels of an empty list or, |
| * less often, singletons without their sentinels; it's up to |
| * the caller to know which is being tested for, here. |
| * |
| * The default implementation decodes and compares one link; |
| * implementations may have more efficient mechanisms. |
| * |
| * @{ |
| */ |
| template<cell::HasCellOperations T> |
| requires(!cell::HasIsSingleton<T>) __always_inline bool is_singleton(T *e) |
| { |
| return e == e->cell_prev(); |
| } |
| |
| template<cell::HasCellOperations T> |
| requires(cell::HasIsSingleton<T>) __always_inline bool is_singleton(T *e) |
| { |
| return e->cell_is_singleton(); |
| } |
| /** @} */ |
| |
| /** |
| * Like is_singleton(), but checks both edges. Useful only for |
| * testing invariants. |
| * |
| * The default implementation decodes and compares both links. |
| */ |
| template<cell::HasCellOperations T> |
| requires(!cell::HasIsSingletonCheck<T>) __always_inline |
| bool is_singleton_check(T *e) |
| { |
| return (e == e->cell_next()) && (e == e->cell_prev()); |
| } |
| |
| template<cell::HasCellOperations T> |
| requires(cell::HasIsSingletonCheck<T>) __always_inline |
| bool is_singleton_check(T *e) |
| { |
| return e->is_singleton_check(); |
| } |
| /** @} */ |
| |
| /** |
| * Doubletons are either singleton collections (with both the sentinel |
| * and the single element satisfying this test) or, less often, a pair |
| * of elements without a sentinel. The caller is expected to know |
| * what's meant by this test. |
| * |
| * The default implementation decodes and compares both links. |
| * @{ |
| */ |
| template<cell::HasCellOperations T> |
| requires(!cell::HasIsDoubleton<T>) __always_inline bool is_doubleton(T *e) |
| { |
| return e->cell_prev() == e->cell_next(); |
| } |
| |
| template<cell::HasCellOperations T> |
| requires(cell::HasIsDoubleton<T>) __always_inline bool is_doubleton(T *e) |
| { |
| return e->cell_is_doubleton(); |
| } |
| /** @} */ |
| |
| /** |
| * Verify linkage invariants. Again, useful only for testing. |
| * |
| * The default implementation decodes all four relevant links. |
| */ |
| template<cell::HasCellOperations T> |
| __always_inline bool is_well_formed(T *e) |
| { |
| return (e == e->cell_prev()->cell_next()) && |
| (e == e->cell_next()->cell_prev()); |
| } |
| |
| /** |
| * Insert a ring of `elem`-ents (typically, a singleton ring) before the |
| * `curr`-ent element (or sentinel) in the ring. In general, you will |
| * probably want to make sure that at most one of `elem` or `curr` |
| * points to a ring with a sentinel node. |
| * |
| * If `curr` is the sentinel, this is appending to the list, in the |
| * sense that the element(s) occupy (or span) the next-most and |
| * prev-least position from the sentinel. |
| * |
| * By symmetry, if `elem` is, instead, the sentinel, then `curr` is |
| * prepended to the list in the same sense. |
| */ |
| template<cell::HasCellOperations Cell> |
| __always_inline void insert_before(Cell *curr, Cell *elem) |
| { |
| curr->cell_prev()->cell_next() = elem->cell_next(); |
| elem->cell_next()->cell_prev() = curr->cell_prev(); |
| curr->cell_prev() = elem; |
| elem->cell_next() = curr; |
| } |
| |
| /** |
| * Emplacement before. This fuses initialization and insertion, so that |
| * |
| * emplace_before(c, e); |
| * |
| * is semantically equivalent to |
| * |
| * e->cell_reset(); insert_before(c, e); |
| * |
| * but spelled in a way that the compiler can understand a bit better, with |
| * less effort spent in provenance and/or alias analysis. |
| */ |
| template<cell::HasCellOperations Cell, typename P> |
| requires std::same_as<P, Cell *> || ds::pointer::proxy::Proxies<P, Cell> |
| __always_inline void emplace_before(P curr, Cell *elem) |
| { |
| auto prev = curr->cell_prev(); |
| elem->cell_next() = curr; |
| elem->cell_prev() = prev; |
| prev->cell_next() = elem; |
| prev = elem; |
| } |
| |
| /** |
| * Emplacement after. This fuses initialization and insertion, so that |
| * |
| * emplace_after(c, e); |
| * |
| * is semantically equivalent to |
| * |
| * e->cell_reset(); insert_before(e, c); |
| * |
| * but spelled in a way that the compiler can understand a bit better, with |
| * less effort spent in provenance and/or alias analysis. |
| */ |
| template<cell::HasCellOperations Cell, typename P> |
| requires std::same_as<P, Cell *> || ds::pointer::proxy::Proxies<P, Cell> |
| __always_inline void emplace_after(P curr, Cell *elem) |
| { |
| auto next = curr->cell_next(); |
| elem->cell_prev() = curr; |
| elem->cell_next() = next; |
| next->cell_prev() = elem; |
| next = elem; |
| } |
| |
| /** |
| * Remove from the list without turning the removed span into a |
| * well-formed ring. This is useful only if that invariant will be |
| * restored later (prior to insertion, at the very least). |
| * |
| * The removed element or span instead retains links into the ring |
| * whence it was removed, but is no longer well-formed, since that ring |
| * no longer references the removed element or span. |
| * |
| * This can be used to remove... |
| * |
| * - a single element (`el == er`) |
| * |
| * - the sentinel (`el == er`), leaving the rest of the ring, if any, |
| * as a sentinel-free ring |
| * |
| * - a span of elements from `el` to `er` via the `next` links; the |
| * removed span is damaged and must be corrected, while the residual |
| * ring remains well-formed. |
| * |
| * In all cases, `el`'s previous element is returned as a handle to the |
| * residual ring. |
| */ |
| template<cell::HasCellOperations Cell> |
| __always_inline Cell *unsafe_remove(Cell *el, Cell *er) |
| { |
| auto p = el->cell_prev(); |
| auto n = er->cell_next(); |
| n->cell_prev() = p; |
| p->cell_next() = n; |
| return p; |
| } |
| |
| template<cell::HasCellOperations Cell> |
| __always_inline Cell *unsafe_remove(Cell *e) |
| { |
| return unsafe_remove(e, e); |
| } |
| |
| /** |
| * Remove a particular element `rem` from the ring, already knowing its |
| * adjacent, previous link `prev`. `prev` remains connected to the ring |
| * but `rem` will no longer be well-formed. Returns a proxy to prev's |
| * next field. |
| */ |
| template<cell::HasCellOperations Cell> |
| __always_inline auto unsafe_remove_link(Cell *prev, Cell *rem) |
| { |
| auto next = rem->cell_next(); |
| auto prevnext = prev->cell_next(); |
| prevnext = next; |
| next->cell_prev() = prev; |
| return prevnext; |
| } |
| |
| /** |
| * Remove from the ring, cleaving the ring into two well-formed rings. |
| * |
| * This can be used to remove... |
| * |
| * - a single element (`el == er`) |
| * |
| * - the sentinel (`el == er`), leaving the rest of the ring, if any, |
| * as a sentinel-free collection |
| * |
| * - a span of elements from `el` to `er` via `next` links; the |
| * removed span is made into a ring and the residual ring is left |
| * well-formed. |
| * |
| * In all cases, `el`'s previous element is returned as a handle to the |
| * residual ring. (The caller must already have a reference to the span |
| * being removed). This is especially useful when `remove`-ing elements |
| * during a `search`, below: overwriting the callback's Cell pointer |
| * (passed by *reference*) will continue the iteration, calling back at |
| * the removed node's successor. |
| * |
| * Removing a singleton from its ring from itself causes no change, as |
| * any would-be residual ring is empty. This corner case requires some |
| * care on occasion. |
| */ |
| template<cell::HasCellOperations Cell> |
| __always_inline Cell *remove(Cell *el, Cell *er) |
| { |
| Cell *p = unsafe_remove(el, er); |
| el->cell_prev() = er; |
| er->cell_next() = el; |
| return p; |
| } |
| |
| template<cell::HasCellOperations Cell> |
| __always_inline Cell *remove(Cell *e) |
| { |
| return remove(e, e); |
| } |
| |
| /** |
| * Search through a span of a ring, inclusively from `from` through |
| * exclusively to `to`, applying `f` to each cons cell in turn. If `f` |
| * returns `true`, the search stops early and returns `true`; otherwise, |
| * search returns `false`. To (side-effectfully) visit every node in the |
| * span, have `f` always return false. |
| */ |
| template<cell::HasCellOperations Cell, typename F> |
| __always_inline bool search(Cell *from, Cell *to, F f) |
| { |
| Cell *elem; |
| for (elem = from; elem != to; elem = elem->cell_next()) |
| { |
| if (f(elem)) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Search through all elements of a ring *except* `elem`. If `elem` is the |
| * sentinel of a ring, then this is, as one expects, a `search` over all |
| * non-sentinel members of the ring. |
| */ |
| template<cell::HasCellOperations Cell, typename F> |
| __always_inline bool search(Cell *elem, F f) |
| { |
| return search(static_cast<Cell *>(elem->cell_next()), elem, f); |
| } |
| |
| /** |
| * Convenience wrapper for a sentinel cons cell, encapsulating some common |
| * patterns. |
| */ |
| template<cell::HasCellOperationsReset CellTemplateArg> |
| struct Sentinel |
| { |
| using Cell = CellTemplateArg; |
| |
| /** |
| * The sentinel node itself. Viewing the ring as a list, this |
| * effectively serves as pointers to the head (next) and to the tail |
| * (prev) of the list. Unlike more traditional nullptr-terminated |
| * lists, though, here, the sentinel participates in the ring. |
| * |
| * This is marked `cheri_no_subobject_bounds` because some of our cons |
| * cell implementations use pointer proxies that rely on the bounds |
| * provided by `this` (which, in turn, is likely to be |
| * `cheri_no_subobject_bounds`) |
| */ |
| Cell sentinel __attribute__((__cheri_no_subobject_bounds__)) = {}; |
| |
| __always_inline void reset() |
| { |
| sentinel.cell_reset(); |
| } |
| |
| __always_inline bool is_empty() |
| { |
| return linked_list::is_singleton(&sentinel); |
| } |
| |
| __always_inline void append(Cell *elem) |
| { |
| linked_list::insert_before(&sentinel, elem); |
| } |
| |
| __always_inline void append_emplace(Cell *elem) |
| { |
| linked_list::emplace_before(&sentinel, elem); |
| } |
| |
| __always_inline void prepend(Cell *elem) |
| { |
| linked_list::insert_before(elem, &sentinel); |
| } |
| |
| __always_inline Cell *first() |
| { |
| return sentinel.cell_next(); |
| } |
| |
| __always_inline Cell *last() |
| { |
| return sentinel.cell_prev(); |
| } |
| |
| __always_inline Cell *unsafe_take_first() |
| { |
| Cell *f = sentinel.cell_next(); |
| linked_list::unsafe_remove_link(&sentinel, f); |
| return f; |
| } |
| |
| __always_inline Cell *take_all() |
| { |
| auto p = linked_list::unsafe_remove(&sentinel); |
| sentinel.cell_reset(); |
| return p; |
| } |
| |
| template<typename F> |
| __always_inline bool search(F f) |
| { |
| return linked_list::search(&sentinel, f); |
| } |
| }; |
| |
| namespace cell |
| { |
| |
| /** Cons cell using two pointers */ |
| class Pointer |
| { |
| Pointer *prev, *next; |
| |
| public: |
| Pointer() |
| { |
| this->cell_reset(); |
| } |
| |
| __always_inline void cell_reset() |
| { |
| prev = next = this; |
| } |
| |
| __always_inline auto cell_next() |
| { |
| return ds::pointer::proxy::Pointer(next); |
| } |
| |
| __always_inline auto cell_prev() |
| { |
| return ds::pointer::proxy::Pointer(prev); |
| } |
| }; |
| static_assert(HasCellOperationsReset<Pointer>); |
| |
| /** |
| * Encode a linked list cons cell as a pair of addresses (but present an |
| * interface in terms of pointers). CHERI bounds on the returned |
| * pointers are inherited from the pointer to `this` cons cell. |
| */ |
| class PtrAddr |
| { |
| ptraddr_t prev, next; |
| |
| public: |
| PtrAddr() |
| { |
| this->cell_reset(); |
| } |
| /* Primops */ |
| |
| __always_inline void cell_reset() |
| { |
| prev = next = CHERI::Capability{this}.address(); |
| } |
| |
| __always_inline auto cell_next() |
| { |
| return ds::pointer::proxy::PtrAddr(this, next); |
| } |
| |
| __always_inline auto cell_prev() |
| { |
| return ds::pointer::proxy::PtrAddr(this, prev); |
| } |
| |
| /* |
| * Specialized implementations that may be slightly fewer |
| * instructions than the generic approaches in terms of the primops. |
| */ |
| |
| __always_inline bool cell_is_singleton() |
| { |
| return prev == CHERI::Capability{this}.address(); |
| } |
| |
| __always_inline bool cell_is_doubleton() |
| { |
| return prev == next; |
| } |
| }; |
| static_assert(HasCellOperationsReset<PtrAddr>); |
| static_assert(HasIsSingleton<PtrAddr>); |
| static_assert(HasIsDoubleton<PtrAddr>); |
| |
| /** |
| * Encode a linked list cons cell as a pair of addresses (but present an |
| * interface in terms of pointers). CHERI bounds on the returned |
| * pointers are inherited from the pointer to `this` cons cell. |
| */ |
| template<ptrdiff_t Offset> |
| class OffsetPtrAddr |
| { |
| ptraddr_t prev, next; |
| |
| public: |
| OffsetPtrAddr() |
| { |
| this->cell_reset(); |
| } |
| |
| /* Primops */ |
| |
| __always_inline void cell_reset() |
| { |
| prev = next = CHERI::Capability{this}.address() - Offset; |
| } |
| |
| __always_inline auto cell_next() |
| { |
| return ds::pointer::proxy::OffsetPtrAddr<Offset, OffsetPtrAddr>( |
| this, next); |
| } |
| |
| __always_inline auto cell_prev() |
| { |
| return ds::pointer::proxy::OffsetPtrAddr<Offset, OffsetPtrAddr>( |
| this, prev); |
| } |
| |
| /* |
| * Specialized implementations that may be slightly fewer |
| * instructions than the generic approaches in terms of the primops. |
| */ |
| |
| __always_inline bool cell_is_singleton() |
| { |
| return prev == CHERI::Capability{this}.address() - Offset; |
| } |
| |
| __always_inline bool cell_is_doubleton() |
| { |
| return prev == next; |
| } |
| }; |
| static_assert(HasCellOperationsReset<OffsetPtrAddr<0>>); |
| static_assert(HasIsSingleton<OffsetPtrAddr<0>>); |
| static_assert(HasIsDoubleton<OffsetPtrAddr<0>>); |
| |
| } // namespace cell |
| |
| } // namespace ds::linked_list |