blob: 5194d2f55a1ef801531935bba429d924b667d008 [file] [log] [blame]
// 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