I tried to design xallocator
— C++ allocator of executable/readable/writable memory. It used along with std::vector
(I need a contiguous storage) to store code, generated by JIT-compiler. For C++11 and later it looks like:
#include <memory>
#include <limits>
#include <new>
#include <utility>
#include <stdexcept>
#include <cstdint>
#include <cstddef>
#include <cassert>
template< typename type >
struct xallocator
{
using value_type = type;
xallocator() = default;
template< typename rhs >
constexpr
xallocator(xallocator< rhs > const &) noexcept
{ ; }
#ifdef PAGESIZE
[[gnu::assume_aligned(PAGESIZE)]] // -DPAGESIZE=`getconf PAGESIZE` for Linux xor -DPAGESIZE="alignof(std::max_align_t)" for Windows
#endif
value_type *
allocate(std::size_t n) const;
void
deallocate(value_type * p, std::size_t n) const;
};
template< typename lhs, typename rhs >
constexpr
bool
operator == (xallocator< lhs > const & /*_lhs*/, xallocator< rhs > const & /*_rhs*/) noexcept
{
return true;
}
template< typename lhs, typename rhs >
constexpr
bool
operator != (xallocator< lhs > const & _lhs, xallocator< rhs > const & _rhs) noexcept
{
return !(_lhs == _rhs);
}
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
template< typename type >
auto
xallocator< type >::allocate(std::size_t n) const
-> value_type *
{
if (n == 0) {
return nullptr;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
::LPVOID const p = ::VirtualAlloc(NULL, n * sizeof(value_type), (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
if (p == NULL) {
throw std::bad_alloc{};
}
#pragma clang diagnostic pop
#ifdef PAGESIZE
assert(0 == (reinterpret_cast< std::uintptr_t >(p) % PAGESIZE));
#endif
return static_cast< value_type * >(p);
}
template< typename type >
void
xallocator< type >::deallocate(value_type * p, std::size_t n) const
{
if (p == nullptr) {
return;
}
if (::VirtualFree(static_cast< ::LPVOID >(p), 0, MEM_RELEASE) == FALSE) {
throw std::bad_alloc{};
}
}
#elif defined(__linux__)
#include <sys/mman.h>
template< typename type >
auto
xallocator< type >::allocate(std::size_t n) const
-> value_type *
{
if (n == 0) {
return nullptr;
}
void * p = ::mmap(nullptr, n * sizeof(value_type), (PROT_READ | PROT_WRITE | PROT_EXEC), (MAP_PRIVATE | MAP_ANONYMOUS), -1, 0); // insecure
if (p == MAP_FAILED) {
throw std::bad_alloc{};
}
#ifdef PAGESIZE
assert(0 == (reinterpret_cast< std::uintptr_t >(p) % PAGESIZE));
#endif
return static_cast< value_type * >(p);
}
template< typename type >
void
xallocator< type >::deallocate(value_type * p, std::size_t n) const
{
if (p == nullptr) {
return;
}
if (::munmap(static_cast< void * >(p), n * sizeof(value_type)) == -1) {
throw std::bad_alloc{};
}
}
#endif
But recently I find, that deallocate()
function shouldn't throw any exceptions. Therefore I safely should mark it by noexcept
specifier.
But what to do if I can't deallocate allocated (by means of corresponding API's) memory in deallocated()
member function due to error code returned? Should I manually call std::terminate
or allow to do it for compiler (by means of throwing std::bad_alloc
into deallocate() noexcept
)? Or should I simply ignore the error?
I sure it is very unlikely, when VirtualFree
/munmap
returns error code, but I should still be correct even in such a rare cases.