blob: 598c726e39fc25a6d9d10a14175608f6129a850f [file] [log] [blame]
// Copyright Microsoft and CHERIoT Contributors.
// SPDX-License-Identifier: MIT
/**
* @file Pointer utilities
*/
#pragma once
#include <cheri.hh>
#include <concepts>
namespace ds::pointer
{
/**
* Offset a pointer by a number of bytes. The return type must be
* explicitly specified by the caller. The type of the displacement offset
* (Offset) is templated so that we can accept both signed and unsigned
* offsets.
*/
template<typename T, typename U>
static inline __always_inline T *offset(U *base, std::integral auto offset)
{
CHERI::Capability c{base};
c.address() += offset;
return c.template cast<void>().template cast<T>();
}
/**
* Compute the unsigned difference in bytes between two pointers' target
* addresses. To be standards-compliant, cursor must be part of the same
* allocation as base and at a higher address.
*/
static inline __always_inline size_t diff(const void *base,
const void *cursor)
{
return static_cast<size_t>(reinterpret_cast<const char *>(cursor) -
reinterpret_cast<const char *>(base));
}
namespace proxy
{
/**
* Proxies<P,T> if P is a proxy object for T*-s.
*/
template<typename P, typename T>
concept Proxies = std::same_as<T, typename P::Type> &&
requires(P &proxy, P &proxy2, T *ptr)
{
/* Probe for operator=(T*) */
{
proxy = ptr
} -> std::same_as<P &>;
/* Probe for operator T*() */
{
ptr == proxy
} -> std::same_as<bool>;
/* TODO: How to probe for operator-> ? */
/* Probe for operator==(T*) */
{
proxy == ptr
} -> std::same_as<bool>;
/* Probe for operator==(P&) */
{
proxy == proxy2
} -> std::same_as<bool>;
/* Probe for operator<=>(T*) */
{
proxy <=> ptr
} -> std::same_as<std::strong_ordering>;
/* Probe for operator<=>(P) */
{
proxy <=> proxy2
} -> std::same_as<std::strong_ordering>;
};
/**
* Pointer references are pointer proxies, shockingly enough.
*/
template<typename T>
class Pointer
{
T *&ref;
public:
using Type = T;
__always_inline Pointer(T *&r) : ref(r) {}
__always_inline operator T *()
{
return ref;
}
__always_inline T *operator->()
{
return *this;
}
__always_inline Pointer<T> &operator=(T *t)
{
ref = t;
return *this;
}
__always_inline Pointer<T> &operator=(Pointer const &p)
{
ref = p.ref;
return *this;
}
__always_inline bool operator==(Pointer &p)
{
return this->ref == p.ref;
}
__always_inline auto operator<=>(Pointer &p)
{
return this->ref <=> p.ref;
}
};
static_assert(Proxies<Pointer<void>, void>);
/**
* Equipped with a context for bounds, an address reference can be a
* proxy for a pointer.
*/
template<typename T>
class PtrAddr
{
CHERI::Capability<void> ctx;
ptraddr_t &ref;
public:
using Type = T;
__always_inline PtrAddr(void *c, ptraddr_t &r) : ctx(c), ref(r) {}
__always_inline operator T *()
{
auto c = ctx;
c.address() = ref;
return c.cast<T>().get();
}
__always_inline T *operator->()
{
return *this;
}
__always_inline PtrAddr &operator=(T *p)
{
ref = CHERI::Capability{p}.address();
return *this;
}
__always_inline PtrAddr &operator=(PtrAddr const &p)
{
ref = p.ref;
return *this;
}
/*
* Since the context is used only for bounds, don't bother
* implicitly converting both proxies up to T*
*/
__always_inline bool operator==(PtrAddr &p)
{
return ref == p.ref;
}
__always_inline auto operator<=>(PtrAddr &p)
{
return ref <=> p.ref;
}
};
static_assert(Proxies<PtrAddr<void>, void>);
/**
* Deduction gude for the common enough case where the context
* type and the represented type are equal.
*/
template<typename T>
PtrAddr(T *, ptraddr_t) -> PtrAddr<T>;
/**
* Like the above, but with a constant offset on the interpretation of
* its addresss fields. This is useful for building points-to-container
* data structures (rather than points-to-member as with the above two).
* The container_of and address-taking operations that move back and
* forth between container and link member should fuse away with the
* offsetting operations herein. You may prefer this if your common or
* fast-paths involve lots of container_of operations.
*/
template<ptrdiff_t Offset, typename T>
class OffsetPtrAddr
{
CHERI::Capability<void> ctx;
ptraddr_t &ref;
public:
using Type = T;
__always_inline OffsetPtrAddr(void *c, ptraddr_t &r)
: ctx(c), ref(r)
{
}
__always_inline operator T *()
{
auto c = ctx;
c.address() = ref + Offset;
return c.cast<T>().get();
}
__always_inline T *operator->()
{
return *this;
}
__always_inline OffsetPtrAddr &operator=(T *p)
{
ref = CHERI::Capability{p}.address() - Offset;
return *this;
}
__always_inline OffsetPtrAddr &operator=(OffsetPtrAddr const &p)
{
ref = p.ref;
return *this;
}
/*
* Since the context is used only for bounds, don't bother
* implicitly converting both proxies up to T*. This also probably
* saves the optimizer the effort of cancelling the Offset
* arithmetic on either side of the comparison.
*/
__always_inline bool operator==(OffsetPtrAddr &p)
{
return ref == p.ref;
}
__always_inline auto operator<=>(OffsetPtrAddr &p)
{
return ref <=> p.ref;
}
};
static_assert(Proxies<OffsetPtrAddr<8, void>, void>);
} // namespace proxy
} // namespace ds::pointer