#pragma once
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <limits>
#include <memory>
#include <new>
#include <utility>
#include <type_traits>
#include "platform.h"
#if ISOSPEC_WE_ARE_ON_WINDOWS || ISOSPEC_WE_ARE_ON_UNIX_YAY
#define ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC 1
#else
#define ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC 0
#endif
#if defined(_WIN32)
#define ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE 0
#else
#define ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE 1
#endif
#if ISOSPEC_WE_ARE_ON_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#define ISOSPEC_UNDEF_NOMINMAX
#endif
#include <windows.h>
#ifdef ISOSPEC_UNDEF_NOMINMAX
#undef NOMINMAX
#undef ISOSPEC_UNDEF_NOMINMAX
#endif
#if defined(_WIN32)
#include <malloc.h>
#endif
#elif ISOSPEC_WE_ARE_ON_UNIX_YAY
#include <sys/mman.h>
#include <unistd.h>
#if defined(__APPLE__) && defined(__MACH__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#endif
#endif
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
namespace aligned_ptr_detail {
inline std::size_t compute_os_page_size() noexcept
{
#if ISOSPEC_WE_ARE_ON_WINDOWS
SYSTEM_INFO si;
GetSystemInfo(&si);
return static_cast<std::size_t>(si.dwPageSize);
#else
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
#endif
}
inline const std::size_t os_page_size_ = compute_os_page_size();
inline std::size_t os_page_size() noexcept
{
return os_page_size_;
}
inline std::size_t round_up_to_page(std::size_t bytes) noexcept
{
const std::size_t ps = os_page_size();
return (bytes + ps - 1) / ps * ps;
}
#ifndef ISOSPEC_ALIGNED_PTR_VM_THRESHOLD
#define ISOSPEC_ALIGNED_PTR_VM_THRESHOLD (std::size_t(256) << 10)
#endif
inline std::size_t vm_backend_threshold() noexcept
{
const std::size_t ps = os_page_size();
return ISOSPEC_ALIGNED_PTR_VM_THRESHOLD < ps ? ps : ISOSPEC_ALIGNED_PTR_VM_THRESHOLD;
}
inline void vm_release_raw(void* ptr, std::size_t bytes) noexcept
{
#if ISOSPEC_WE_ARE_ON_WINDOWS
(void)bytes;
::VirtualFree(ptr, 0, MEM_RELEASE);
#else
::munmap(ptr, bytes);
#endif
}
#if ISOSPEC_WE_ARE_ON_WINDOWS
struct vm_region {
void* base = nullptr;
std::size_t reserved = 0;
std::size_t committed = 0;
};
inline std::size_t reservation_size_for(std::size_t bytes) noexcept
{
constexpr std::size_t min_reservation = std::size_t{1} << 26; constexpr std::size_t growth_factor = 16;
std::size_t reserved = round_up_to_page(bytes) * growth_factor;
if (reserved < min_reservation)
reserved = min_reservation;
return reserved;
}
inline vm_region vm_create(std::size_t bytes)
{
vm_region r;
r.committed = round_up_to_page(bytes);
r.reserved = reservation_size_for(bytes);
r.base = ::VirtualAlloc(nullptr, r.reserved, MEM_RESERVE, PAGE_NOACCESS);
if (!r.base)
throw std::bad_alloc();
if (r.committed > 0 &&
!::VirtualAlloc(r.base, r.committed, MEM_COMMIT, PAGE_READWRITE)) {
::VirtualFree(r.base, 0, MEM_RELEASE);
throw std::bad_alloc();
}
return r;
}
inline void vm_destroy(vm_region& r) noexcept
{
if (r.base)
::VirtualFree(r.base, 0, MEM_RELEASE);
r = vm_region{};
}
inline void vm_resize(vm_region& r, std::size_t new_bytes)
{
const std::size_t new_committed = round_up_to_page(new_bytes);
if (new_committed == r.committed)
return;
if (new_committed > r.reserved) {
vm_region moved = vm_create(new_bytes);
std::memcpy(moved.base, r.base, r.committed);
vm_destroy(r);
r = moved;
return;
}
if (new_committed > r.committed) {
if (!::VirtualAlloc(static_cast<char*>(r.base) + r.committed,
new_committed - r.committed, MEM_COMMIT, PAGE_READWRITE))
throw std::bad_alloc();
} else {
::VirtualFree(static_cast<char*>(r.base) + new_committed,
r.committed - new_committed, MEM_DECOMMIT);
}
r.committed = new_committed;
}
#else
struct vm_region {
void* base = nullptr;
std::size_t size = 0;
};
inline vm_region vm_create(std::size_t bytes)
{
vm_region r;
r.size = round_up_to_page(bytes);
r.base = ::mmap(nullptr, r.size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (r.base == MAP_FAILED)
throw std::bad_alloc();
return r;
}
inline void vm_destroy(vm_region& r) noexcept
{
if (r.base)
::munmap(r.base, r.size);
r = vm_region{};
}
#if defined(__APPLE__) && defined(__MACH__)
inline void vm_resize(vm_region& r, std::size_t new_bytes)
{
const std::size_t new_size = round_up_to_page(new_bytes);
if (new_size == r.size)
return;
if (new_size < r.size) {
::munmap(static_cast<char*>(r.base) + new_size, r.size - new_size);
r.size = new_size;
return;
}
mach_vm_address_t new_addr = 0;
if (mach_vm_allocate(mach_task_self(), &new_addr, new_size, VM_FLAGS_ANYWHERE) != KERN_SUCCESS)
throw std::bad_alloc();
mach_vm_address_t target = new_addr;
vm_prot_t cur_prot, max_prot;
const kern_return_t kr = mach_vm_remap(
mach_task_self(), &target, r.size, 0, VM_FLAGS_OVERWRITE,
mach_task_self(), reinterpret_cast<mach_vm_address_t>(r.base),
FALSE ,
&cur_prot, &max_prot, VM_INHERIT_COPY);
if (kr != KERN_SUCCESS) {
mach_vm_deallocate(mach_task_self(), new_addr, new_size);
throw std::bad_alloc();
}
::munmap(r.base, r.size);
r.base = reinterpret_cast<void*>(new_addr);
r.size = new_size;
}
#else
#if defined(__SANITIZE_THREAD__)
#define ISOSPEC_ALIGNED_PTR_UNDER_TSAN 1
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define ISOSPEC_ALIGNED_PTR_UNDER_TSAN 1
#endif
#endif
#if defined(MREMAP_MAYMOVE) && !defined(ISOSPEC_ALIGNED_PTR_UNDER_TSAN)
#define ISOSPEC_ALIGNED_PTR_USE_MREMAP 1
#endif
inline void vm_resize(vm_region& r, std::size_t new_bytes)
{
const std::size_t new_size = round_up_to_page(new_bytes);
if (new_size == r.size)
return;
#if !defined(ISOSPEC_ALIGNED_PTR_USE_MREMAP)
if (new_size < r.size) {
::munmap(static_cast<char*>(r.base) + new_size, r.size - new_size);
r.size = new_size;
return;
}
vm_region moved = vm_create(new_bytes);
std::memcpy(moved.base, r.base, (std::min)(r.size, moved.size));
vm_destroy(r);
r = moved;
#else
void* p = ::mremap(r.base, r.size, new_size, MREMAP_MAYMOVE);
if (p == MAP_FAILED)
throw std::bad_alloc();
r.base = p;
r.size = new_size;
#endif
}
#endif #endif
} #endif
template<class T, std::size_t Alignment>
class aligned_unique_ptr {
static_assert((Alignment & (Alignment - 1)) == 0); static_assert(Alignment >= alignof(T));
static_assert(Alignment <= 4096, "Alignment must not exceed the smallest page size assumed across supported platforms");
static_assert(std::is_trivial_v<T>, "aligned_unique_ptr never runs T's constructor/destructor; T must be trivial");
T* ptr_ = nullptr;
std::size_t capacity_bytes_ = 0;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
bool is_mapped_ = false;
aligned_ptr_detail::vm_region region_{};
#endif
static std::size_t round_up_to_alignment(std::size_t bytes) noexcept
{
return (bytes + Alignment - 1) / Alignment * Alignment;
}
static std::size_t bytes_for(std::size_t n)
{
constexpr std::size_t max_size = (std::numeric_limits<std::size_t>::max)();
if (n > max_size / sizeof(T))
throw std::bad_alloc();
const std::size_t bytes = n * sizeof(T);
if (bytes > max_size - (Alignment - 1))
throw std::bad_alloc();
return bytes;
}
static T* allocate_small(std::size_t n)
{
if (n == 0)
return nullptr;
const std::size_t bytes = bytes_for(n);
#if ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE
void* p = std::aligned_alloc(Alignment, round_up_to_alignment(bytes));
#else
void* p = ::_aligned_malloc(bytes, Alignment);
#endif
if (!p)
throw std::bad_alloc();
return static_cast<T*>(p);
}
static void free_small(void* p) noexcept
{
#if ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE
std::free(p);
#else
::_aligned_free(p);
#endif
}
void free_current() noexcept
{
if (!ptr_)
return;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
if (is_mapped_) {
aligned_ptr_detail::vm_destroy(region_);
return;
}
#endif
free_small(ptr_);
}
static void deleter_free(void* p, std::size_t) noexcept
{
std::free(p);
}
static void deleter_small(void* p, std::size_t) noexcept
{
free_small(p);
}
public:
static constexpr std::size_t alignment = Alignment;
struct release_result {
T* ptr;
std::size_t size;
void (*deleter)(void*, std::size_t) noexcept;
};
T* allocate(std::size_t n)
{
return allocate_small(n);
}
explicit aligned_unique_ptr(std::size_t n)
: ptr_(allocate(n))
, capacity_bytes_(n * sizeof(T))
{}
aligned_unique_ptr() noexcept
: ptr_(nullptr)
{}
~aligned_unique_ptr()
{
free_current();
}
aligned_unique_ptr(const aligned_unique_ptr&) = delete;
aligned_unique_ptr& operator=(const aligned_unique_ptr&) = delete;
aligned_unique_ptr(aligned_unique_ptr&& other) noexcept
: ptr_(std::exchange(other.ptr_, nullptr))
, capacity_bytes_(std::exchange(other.capacity_bytes_, 0))
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
, is_mapped_(std::exchange(other.is_mapped_, false))
, region_(std::exchange(other.region_, aligned_ptr_detail::vm_region{}))
#endif
{}
aligned_unique_ptr& operator=(aligned_unique_ptr&& other) noexcept
{
if (this != &other) {
free_current();
ptr_ = std::exchange(other.ptr_, nullptr);
capacity_bytes_ = std::exchange(other.capacity_bytes_, 0);
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
is_mapped_ = std::exchange(other.is_mapped_, false);
region_ = std::exchange(other.region_, aligned_ptr_detail::vm_region{});
#endif
}
return *this;
}
T* get() noexcept
{
return std::assume_aligned<Alignment>(ptr_);
}
const T* get() const noexcept
{
return std::assume_aligned<Alignment>(ptr_);
}
T& operator[](std::size_t i) noexcept
{
return get()[i];
}
const T& operator[](std::size_t i) const noexcept
{
return get()[i];
}
explicit operator bool() const noexcept
{
return ptr_ != nullptr;
}
void reset(std::size_t n)
{
free_current();
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
is_mapped_ = false;
region_ = aligned_ptr_detail::vm_region{};
#endif
if (n > 0) {
ptr_ = allocate_small(n);
capacity_bytes_ = n * sizeof(T);
} else {
ptr_ = nullptr;
capacity_bytes_ = 0;
}
}
void realloc(std::size_t n)
{
const std::size_t new_bytes = n == 0 ? 0 : bytes_for(n);
if (new_bytes == 0) {
free_current();
ptr_ = nullptr;
capacity_bytes_ = 0;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
is_mapped_ = false;
region_ = aligned_ptr_detail::vm_region{};
#endif
return;
}
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
if (is_mapped_) {
aligned_ptr_detail::vm_resize(region_, new_bytes);
ptr_ = static_cast<T*>(region_.base);
capacity_bytes_ = new_bytes;
return;
}
if (new_bytes >= aligned_ptr_detail::vm_backend_threshold()) {
aligned_ptr_detail::vm_region r = aligned_ptr_detail::vm_create(new_bytes);
if (ptr_) {
std::memcpy(r.base, ptr_, (std::min)(capacity_bytes_, new_bytes));
free_small(ptr_);
}
region_ = r;
ptr_ = static_cast<T*>(region_.base);
capacity_bytes_ = new_bytes;
is_mapped_ = true;
return;
}
#endif
T* new_ptr = allocate_small(n);
if (ptr_) {
std::memcpy(new_ptr, ptr_, (std::min)(capacity_bytes_, new_bytes));
free_small(ptr_);
}
ptr_ = new_ptr;
capacity_bytes_ = new_bytes;
}
T* release()
{
if (!ptr_) {
capacity_bytes_ = 0;
return nullptr;
}
bool needs_materialize = !ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
needs_materialize = needs_materialize || is_mapped_;
#endif
if (needs_materialize) {
void* copy = std::malloc(capacity_bytes_);
if (!copy)
throw std::bad_alloc();
std::memcpy(copy, ptr_, capacity_bytes_);
free_current();
ptr_ = nullptr;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
is_mapped_ = false;
region_ = aligned_ptr_detail::vm_region{};
#endif
capacity_bytes_ = 0;
return static_cast<T*>(copy);
}
capacity_bytes_ = 0;
return std::assume_aligned<Alignment>(std::exchange(ptr_, nullptr));
}
release_result release_with_deleter() noexcept
{
T* p = std::exchange(ptr_, nullptr);
std::size_t bytes = capacity_bytes_;
capacity_bytes_ = 0;
#if ISOSPEC_ALIGNED_PTR_HAVE_VM_REALLOC
if (is_mapped_) {
is_mapped_ = false;
#if ISOSPEC_WE_ARE_ON_WINDOWS
const std::size_t region_bytes = region_.reserved;
#else
const std::size_t region_bytes = region_.size;
#endif
region_ = aligned_ptr_detail::vm_region{};
return release_result{p, region_bytes, &aligned_ptr_detail::vm_release_raw};
}
#endif
#if ISOSPEC_ALIGNED_PTR_SMALL_BACKEND_FREE_COMPATIBLE
return release_result{p, bytes, &deleter_free};
#else
return release_result{p, bytes, &deleter_small};
#endif
}
};