janwas
4663ac0fe7
the old debug_assert always ran and tested the expression, which slows down release builds. wrapping them in #ifndef NDEBUG is clumsy. the new ASSERT behaves like assert and ENSURE like the old debug_assert. Let's change any time-critical but not-super-important ENSURE to ASSERT to speed up release builds. (already done in bits.h and unique_range.h) This was SVN commit r9362.
183 lines
4.9 KiB
C++
183 lines
4.9 KiB
C++
#ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE
|
|
#define INCLUDED_ALLOCATORS_UNIQUE_RANGE
|
|
|
|
#include "lib/lib_api.h"
|
|
|
|
// we usually don't hold multiple references to allocations, so unique_ptr
|
|
// can be used instead of the more complex (ICC generated incorrect code on
|
|
// 2 occasions) and expensive shared_ptr.
|
|
// a custom deleter is required because allocators such as ReserveAddressSpace need to
|
|
// pass the size to their deleter. we want to mix pointers from various allocators, but
|
|
// unique_ptr's deleter is fixed at compile-time, so it would need to be general enough
|
|
// to handle all allocators.
|
|
// storing the size and a function pointer would be one such solution, with the added
|
|
// bonus of no longer requiring a complete type at the invocation of ~unique_ptr.
|
|
// however, this inflates the pointer size to 3 words. if only a few allocator types
|
|
// are needed, we can replace the function pointer with an index stashed into the
|
|
// lower bits of the pointer (safe because allocations are always aligned to the
|
|
// word size).
|
|
typedef intptr_t IdxDeleter;
|
|
|
|
// no-op deleter (use when returning part of an existing allocation)
|
|
// must be zero because reset() sets address (which includes idxDeleter) to zero.
|
|
static const IdxDeleter idxDeleterNone = 0;
|
|
|
|
static const IdxDeleter idxDeleterAligned = 1;
|
|
|
|
// (temporary value to prevent concurrent calls to AddUniqueRangeDeleter)
|
|
static const IdxDeleter idxDeleterBusy = -IdxDeleter(1);
|
|
|
|
// governs the maximum number of IdxDeleter and each pointer's alignment requirements
|
|
static const IdxDeleter idxDeleterBits = 0x7;
|
|
|
|
typedef void (*UniqueRangeDeleter)(void* pointer, size_t size);
|
|
|
|
/**
|
|
* @return the next available IdxDeleter and associate it with the deleter.
|
|
* halts the program if the idxDeleterBits limit has been reached.
|
|
*
|
|
* thread-safe, but no attempt is made to detect whether the deleter has already been
|
|
* registered (would require a mutex). each allocator must ensure they only call this once.
|
|
**/
|
|
LIB_API IdxDeleter AddUniqueRangeDeleter(UniqueRangeDeleter deleter);
|
|
|
|
LIB_API void CallUniqueRangeDeleter(void* pointer, size_t size, IdxDeleter idxDeleter) throw();
|
|
|
|
|
|
// unfortunately, unique_ptr allows constructing without a custom deleter. to ensure callers can
|
|
// rely upon pointers being associated with a size, we introduce a `UniqueRange' replacement.
|
|
// its interface is identical to unique_ptr except for the constructors, the addition of
|
|
// size() and the removal of operator bool (which avoids implicit casts to int).
|
|
class UniqueRange
|
|
{
|
|
public:
|
|
typedef void* pointer;
|
|
typedef void element_type;
|
|
|
|
UniqueRange()
|
|
{
|
|
Set(0, 0, idxDeleterNone);
|
|
}
|
|
|
|
UniqueRange(pointer p, size_t size, IdxDeleter deleter)
|
|
{
|
|
Set(p, size, deleter);
|
|
}
|
|
|
|
UniqueRange(RVALUE_REF(UniqueRange) rvalue)
|
|
{
|
|
UniqueRange& lvalue = LVALUE(rvalue);
|
|
address_ = lvalue.address_;
|
|
size_ = lvalue.size_;
|
|
lvalue.address_ = 0;
|
|
}
|
|
|
|
UniqueRange& operator=(RVALUE_REF(UniqueRange) rvalue)
|
|
{
|
|
UniqueRange& lvalue = LVALUE(rvalue);
|
|
if(this != &lvalue)
|
|
{
|
|
Delete();
|
|
address_ = lvalue.address_;
|
|
size_ = lvalue.size_;
|
|
lvalue.address_ = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
~UniqueRange()
|
|
{
|
|
Delete();
|
|
}
|
|
|
|
pointer get() const
|
|
{
|
|
return pointer(address_ & ~idxDeleterBits);
|
|
}
|
|
|
|
IdxDeleter get_deleter() const
|
|
{
|
|
return IdxDeleter(address_ & idxDeleterBits);
|
|
}
|
|
|
|
size_t size() const
|
|
{
|
|
return size_;
|
|
}
|
|
|
|
// side effect: subsequent get_deleter will return idxDeleterNone
|
|
pointer release() // relinquish ownership
|
|
{
|
|
pointer ret = get();
|
|
address_ = 0;
|
|
return ret;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
Delete();
|
|
address_ = 0;
|
|
}
|
|
|
|
void reset(pointer p, size_t size, IdxDeleter deleter)
|
|
{
|
|
Delete();
|
|
Set(p, size, deleter);
|
|
}
|
|
|
|
void swap(UniqueRange& rhs)
|
|
{
|
|
std::swap(address_, rhs.address_);
|
|
std::swap(size_, rhs.size_);
|
|
}
|
|
|
|
// don't define construction and assignment from lvalue,
|
|
// but the declarations must be accessible
|
|
UniqueRange(const UniqueRange&);
|
|
UniqueRange& operator=(const UniqueRange&);
|
|
|
|
private:
|
|
void Set(pointer p, size_t size, IdxDeleter deleter)
|
|
{
|
|
ASSERT((uintptr_t(p) & idxDeleterBits) == 0);
|
|
ASSERT(deleter <= idxDeleterBits);
|
|
|
|
address_ = uintptr_t(p) | deleter;
|
|
size_ = size;
|
|
|
|
ASSERT(get() == p);
|
|
ASSERT(get_deleter() == deleter);
|
|
ASSERT(this->size() == size);
|
|
}
|
|
|
|
void Delete()
|
|
{
|
|
CallUniqueRangeDeleter(get(), size(), get_deleter());
|
|
}
|
|
|
|
// (IdxDeleter is stored in the lower bits of address since size might not even be a multiple of 4.)
|
|
uintptr_t address_;
|
|
size_t size_;
|
|
};
|
|
|
|
namespace std {
|
|
|
|
static inline void swap(UniqueRange& p1, UniqueRange& p2)
|
|
{
|
|
p1.swap(p2);
|
|
}
|
|
|
|
static inline void swap(RVALUE_REF(UniqueRange) p1, UniqueRange& p2)
|
|
{
|
|
p2.swap(LVALUE(p1));
|
|
}
|
|
|
|
static inline void swap(UniqueRange& p1, RVALUE_REF(UniqueRange) p2)
|
|
{
|
|
p1.swap(LVALUE(p2));
|
|
}
|
|
|
|
}
|
|
|
|
#endif // #ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE
|