SP: introduce CapPtr<> wrapper type & bounds taxonomy
This commit just fills out ds/ptrwrap.h with the new types and adds utility methods to ds/address.h.
This commit is contained in:
committed by
Nathaniel Wesley Filardo
parent
c6036f3808
commit
294887ad74
268
src/ds/ptrwrap.h
268
src/ds/ptrwrap.h
@@ -15,4 +15,272 @@ namespace snmalloc
|
||||
|
||||
template<typename T>
|
||||
using AtomicPointer = std::atomic<T*>;
|
||||
|
||||
/**
|
||||
* Summaries of StrictProvenance metadata. We abstract away the particular
|
||||
* size and any offset into the bounds.
|
||||
*
|
||||
* CBArena is as powerful as our pointers get: they're results from mmap(),
|
||||
* and so confer as much authority as the kernel has given us.
|
||||
*
|
||||
* CBChunk is restricted to either a single chunk (SUPERSLAB_SIZE) or perhaps
|
||||
* to several if we've requesed a large allocation (see capptr_chunk_is_alloc
|
||||
* and its uses).
|
||||
*
|
||||
* CBChunkD is curious: we often use CBArena-bounded pointers to derive
|
||||
* pointers to Allocslab metadata, and on most fast paths these pointers end
|
||||
* up being ephemeral. As such, on NDEBUG builds, we elide the capptr_bounds
|
||||
* that would bound these to chunks and instead just unsafely inherit the
|
||||
* CBArena bounds. The use of CBChunkD thus helps to ensure that we
|
||||
* eventually do invoke capptr_bounds when these pointers end up being longer
|
||||
* lived!
|
||||
*
|
||||
* *E forms are "exported" and have had platform constraints applied. That
|
||||
* means, for example, on CheriBSD, that they have had their VMMAP permission
|
||||
* stripped.
|
||||
*
|
||||
* Yes, I wish the start-of-comment characters were aligned below as well.
|
||||
* I blame clang format.
|
||||
*/
|
||||
enum capptr_bounds
|
||||
{
|
||||
/* Spatial Notes */
|
||||
CBArena, /* Arena */
|
||||
CBChunkD, /* Arena Chunk-bounded in debug; internal use only! */
|
||||
CBChunk, /* Chunk */
|
||||
CBChunkE, /* Chunk (+ platform constraints) */
|
||||
CBAlloc, /* Alloc */
|
||||
CBAllocE /* Alloc (+ platform constraints) */
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute the "exported" variant of a capptr_bounds annotation. This is
|
||||
* used by the PAL's capptr_export function to compute its return value's
|
||||
* annotation.
|
||||
*/
|
||||
template<capptr_bounds B>
|
||||
constexpr capptr_bounds capptr_export_type()
|
||||
{
|
||||
static_assert(
|
||||
(B == CBChunk) || (B == CBAlloc), "capptr_export_type of bad type");
|
||||
|
||||
switch (B)
|
||||
{
|
||||
case CBChunk:
|
||||
return CBChunkE;
|
||||
case CBAlloc:
|
||||
return CBAllocE;
|
||||
}
|
||||
}
|
||||
|
||||
template<capptr_bounds BI, capptr_bounds BO>
|
||||
constexpr bool capptr_is_bounds_refinement()
|
||||
{
|
||||
switch (BI)
|
||||
{
|
||||
case CBAllocE:
|
||||
return BO == CBAllocE;
|
||||
case CBAlloc:
|
||||
return BO == CBAlloc;
|
||||
case CBChunkE:
|
||||
return BO == CBAllocE || BO == CBChunkE;
|
||||
case CBChunk:
|
||||
return BO == CBAlloc || BO == CBChunk || BO == CBChunkD;
|
||||
case CBChunkD:
|
||||
return BO == CBAlloc || BO == CBChunk || BO == CBChunkD;
|
||||
case CBArena:
|
||||
return BO == CBAlloc || BO == CBChunk || BO == CBChunkD ||
|
||||
BO == CBArena;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer annotated with a "phantom type parameter" carrying a static
|
||||
* summary of its StrictProvenance metadata.
|
||||
*/
|
||||
template<typename T, capptr_bounds bounds>
|
||||
struct CapPtr
|
||||
{
|
||||
T* unsafe_capptr;
|
||||
|
||||
/**
|
||||
* nullptr is implicitly constructable at any bounds type
|
||||
*/
|
||||
CapPtr(const std::nullptr_t n) : unsafe_capptr(n) {}
|
||||
|
||||
CapPtr() : CapPtr(nullptr) {}
|
||||
|
||||
/**
|
||||
* all other constructions must be explicit
|
||||
*/
|
||||
explicit CapPtr(T* p) : unsafe_capptr(p) {}
|
||||
|
||||
/**
|
||||
* Allow static_cast<>-s that preserve bounds but vary the target type.
|
||||
*/
|
||||
template<typename U>
|
||||
SNMALLOC_FAST_PATH CapPtr<U, bounds> as_static()
|
||||
{
|
||||
return CapPtr<U, bounds>(static_cast<U*>(this->unsafe_capptr));
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH CapPtr<void, bounds> as_void()
|
||||
{
|
||||
return this->as_static<void>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A more aggressive bounds-preserving cast, using reinterpret_cast
|
||||
*/
|
||||
template<typename U>
|
||||
SNMALLOC_FAST_PATH CapPtr<U, bounds> as_reinterpret()
|
||||
{
|
||||
return CapPtr<U, bounds>(reinterpret_cast<U*>(this->unsafe_capptr));
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator==(const CapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr == rhs.unsafe_capptr;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator!=(const CapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr != rhs.unsafe_capptr;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator<(const CapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr < rhs.unsafe_capptr;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH T* operator->() const
|
||||
{
|
||||
/*
|
||||
* CBAllocE bounds are associated with objects coming from or going to the
|
||||
* client; we should be doing nothing with them.
|
||||
*/
|
||||
static_assert(bounds != CBAllocE);
|
||||
return this->unsafe_capptr;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(CapPtr<void, CBArena>) == sizeof(void*));
|
||||
static_assert(alignof(CapPtr<void, CBArena>) == alignof(void*));
|
||||
|
||||
template<typename T>
|
||||
using CapPtrCBArena = CapPtr<T, CBArena>;
|
||||
|
||||
template<typename T>
|
||||
using CapPtrCBChunk = CapPtr<T, CBChunk>;
|
||||
|
||||
template<typename T>
|
||||
using CapPtrCBChunkE = CapPtr<T, CBChunkE>;
|
||||
|
||||
template<typename T>
|
||||
using CapPtrCBAlloc = CapPtr<T, CBAlloc>;
|
||||
|
||||
/**
|
||||
* Sometimes (with large allocations) we really mean the entire chunk (or even
|
||||
* several chunks) to be the allocation.
|
||||
*/
|
||||
template<typename T>
|
||||
SNMALLOC_FAST_PATH CapPtr<T, CBAllocE>
|
||||
capptr_chunk_is_alloc(CapPtr<T, CBChunkE> p)
|
||||
{
|
||||
return CapPtr<T, CBAlloc>(p.unsafe_capptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* With all the bounds and constraints in place, it's safe to extract a void
|
||||
* pointer (to reveal to the client).
|
||||
*/
|
||||
SNMALLOC_FAST_PATH void* capptr_reveal(CapPtr<void, CBAllocE> p)
|
||||
{
|
||||
return p.unsafe_capptr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Wrap a std::atomic<T*> with bounds annotation and speak in terms of
|
||||
* bounds-annotated pointers at the interface.
|
||||
*
|
||||
* Note the membranous sleight of hand being pulled here: this class puts
|
||||
* annotations around an un-annotated std::atomic<T*>, to appease C++, yet
|
||||
* will expose or consume only CapPtr<T> with the same bounds annotation.
|
||||
*/
|
||||
template<typename T, capptr_bounds bounds>
|
||||
struct AtomicCapPtr
|
||||
{
|
||||
std::atomic<T*> unsafe_capptr;
|
||||
|
||||
/**
|
||||
* nullptr is constructable at any bounds type
|
||||
*/
|
||||
AtomicCapPtr(const std::nullptr_t n) : unsafe_capptr(n) {}
|
||||
|
||||
/**
|
||||
* Interconversion with CapPtr
|
||||
*/
|
||||
AtomicCapPtr(CapPtr<T, bounds> p) : unsafe_capptr(p.unsafe_capptr) {}
|
||||
|
||||
operator CapPtr<T, bounds>() const noexcept
|
||||
{
|
||||
return CapPtr<T, bounds>(this->unsafe_capptr);
|
||||
}
|
||||
|
||||
// Our copy-assignment operator follows std::atomic and returns a copy of
|
||||
// the RHS. Clang finds this surprising; we suppress the warning.
|
||||
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
|
||||
CapPtr<T, bounds> operator=(CapPtr<T, bounds> p) noexcept
|
||||
{
|
||||
this->store(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH CapPtr<T, bounds>
|
||||
load(std::memory_order order = std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
return CapPtr<T, bounds>(this->unsafe_capptr.load(order));
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH void store(
|
||||
CapPtr<T, bounds> desired,
|
||||
std::memory_order order = std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
this->unsafe_capptr.store(desired.unsafe_capptr, order);
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH CapPtr<T, bounds> exchange(
|
||||
CapPtr<T, bounds> desired,
|
||||
std::memory_order order = std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
return CapPtr<T, bounds>(
|
||||
this->unsafe_capptr.exchange(desired.unsafe_capptr, order));
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator==(const AtomicCapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr == rhs.unsafe_capptr;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator!=(const AtomicCapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr != rhs.unsafe_capptr;
|
||||
}
|
||||
|
||||
SNMALLOC_FAST_PATH bool operator<(const AtomicCapPtr& rhs) const
|
||||
{
|
||||
return this->unsafe_capptr < rhs.unsafe_capptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using AtomicCapPtrCBArena = AtomicCapPtr<T, CBArena>;
|
||||
|
||||
template<typename T>
|
||||
using AtomicCapPtrCBChunk = AtomicCapPtr<T, CBChunk>;
|
||||
|
||||
template<typename T>
|
||||
using AtomicCapPtrCBAlloc = AtomicCapPtr<T, CBAlloc>;
|
||||
|
||||
} // namespace snmalloc
|
||||
|
||||
Reference in New Issue
Block a user