374 lines
13 KiB
D
374 lines
13 KiB
D
/**
|
|
* D binding to C++ std::allocator.
|
|
*
|
|
* Copyright: Copyright (c) 2019 D Language Foundation
|
|
* License: Distributed under the
|
|
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
|
|
* (See accompanying file LICENSE)
|
|
* Authors: Manu Evans
|
|
* Source: $(DRUNTIMESRC core/stdcpp/allocator.d)
|
|
*/
|
|
|
|
module core.stdcpp.allocator;
|
|
|
|
import core.stdcpp.new_;
|
|
import core.stdcpp.xutility : StdNamespace, __cpp_sized_deallocation, __cpp_aligned_new;
|
|
|
|
extern(C++, (StdNamespace)):
|
|
|
|
/**
|
|
* Allocators are classes that define memory models to be used by some parts of
|
|
* the C++ Standard Library, and most specifically, by STL containers.
|
|
*/
|
|
extern(C++, class)
|
|
struct allocator(T)
|
|
{
|
|
static assert(!is(T == const), "The C++ Standard forbids containers of const elements because allocator!(const T) is ill-formed.");
|
|
static assert(!is(T == immutable), "immutable is not representable in C++");
|
|
static assert(!is(T == class), "Instantiation with `class` is not supported; D can't mangle the base (non-pointer) type of a class. Use `extern (C++, class) struct T { ... }` instead.");
|
|
extern(D):
|
|
|
|
///
|
|
this(U)(ref allocator!U) {}
|
|
|
|
///
|
|
alias size_type = size_t;
|
|
///
|
|
alias difference_type = ptrdiff_t;
|
|
///
|
|
alias pointer = T*;
|
|
///
|
|
alias value_type = T;
|
|
|
|
///
|
|
enum propagate_on_container_move_assignment = true;
|
|
///
|
|
enum is_always_equal = true;
|
|
|
|
///
|
|
alias rebind(U) = allocator!U;
|
|
|
|
version (CppRuntime_Microsoft)
|
|
{
|
|
import core.stdcpp.xutility : _MSC_VER;
|
|
|
|
///
|
|
T* allocate(size_t count) @nogc
|
|
{
|
|
static if (_MSC_VER <= 1800)
|
|
{
|
|
import core.stdcpp.xutility : _Xbad_alloc;
|
|
if (count == 0)
|
|
return null;
|
|
void* mem;
|
|
if ((size_t.max / T.sizeof < count) || (mem = __cpp_new(count * T.sizeof)) is null)
|
|
_Xbad_alloc();
|
|
return cast(T*)mem;
|
|
}
|
|
else
|
|
{
|
|
enum _Align = _New_alignof!T;
|
|
|
|
static size_t _Get_size_of_n(T)(const size_t _Count)
|
|
{
|
|
static if (T.sizeof == 1)
|
|
return _Count;
|
|
else
|
|
{
|
|
enum size_t _Max_possible = size_t.max / T.sizeof;
|
|
return _Max_possible < _Count ? size_t.max : _Count * T.sizeof;
|
|
}
|
|
}
|
|
|
|
const size_t _Bytes = _Get_size_of_n!T(count);
|
|
if (_Bytes == 0)
|
|
return null;
|
|
|
|
static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
{
|
|
version (INTEL_ARCH)
|
|
{
|
|
if (_Bytes >= _Big_allocation_threshold)
|
|
return cast(T*)_Allocate_manually_vector_aligned(_Bytes);
|
|
}
|
|
return cast(T*)__cpp_new(_Bytes);
|
|
}
|
|
else
|
|
{
|
|
size_t _Passed_align = _Align;
|
|
version (INTEL_ARCH)
|
|
{
|
|
if (_Bytes >= _Big_allocation_threshold)
|
|
_Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align;
|
|
}
|
|
return cast(T*)__cpp_new_aligned(_Bytes, cast(align_val_t)_Passed_align);
|
|
}
|
|
}
|
|
}
|
|
///
|
|
void deallocate(T* ptr, size_t count) @nogc
|
|
{
|
|
static if (_MSC_VER <= 1800)
|
|
{
|
|
__cpp_delete(ptr);
|
|
}
|
|
else
|
|
{
|
|
// this is observed from VS2017
|
|
void* _Ptr = ptr;
|
|
size_t _Bytes = T.sizeof * count;
|
|
|
|
enum _Align = _New_alignof!T;
|
|
static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
{
|
|
version (INTEL_ARCH)
|
|
{
|
|
if (_Bytes >= _Big_allocation_threshold)
|
|
_Adjust_manually_vector_aligned(_Ptr, _Bytes);
|
|
}
|
|
static if (_MSC_VER <= 1900)
|
|
__cpp_delete(ptr);
|
|
else
|
|
__cpp_delete_size(_Ptr, _Bytes);
|
|
}
|
|
else
|
|
{
|
|
size_t _Passed_align = _Align;
|
|
version (INTEL_ARCH)
|
|
{
|
|
if (_Bytes >= _Big_allocation_threshold)
|
|
_Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align;
|
|
}
|
|
__cpp_delete_size_aligned(_Ptr, _Bytes, cast(align_val_t)_Passed_align);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
enum size_t max_size = size_t.max / T.sizeof;
|
|
}
|
|
else version (CppRuntime_Gcc)
|
|
{
|
|
///
|
|
T* allocate(size_t count, const(void)* = null) @nogc
|
|
{
|
|
// if (count > max_size)
|
|
// std::__throw_bad_alloc();
|
|
|
|
static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof);
|
|
else
|
|
return cast(T*)__cpp_new(count * T.sizeof);
|
|
}
|
|
///
|
|
void deallocate(T* ptr, size_t count) @nogc
|
|
{
|
|
// NOTE: GCC doesn't seem to use the sized delete when it's available...
|
|
|
|
static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
__cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof);
|
|
else
|
|
__cpp_delete(cast(void*)ptr);
|
|
}
|
|
|
|
///
|
|
enum size_t max_size = (ptrdiff_t.max < size_t.max ? cast(size_t)ptrdiff_t.max : size_t.max) / T.sizeof;
|
|
}
|
|
else version (CppRuntime_Clang)
|
|
{
|
|
///
|
|
T* allocate(size_t count, const(void)* = null) @nogc
|
|
{
|
|
// if (count > max_size)
|
|
// __throw_length_error("allocator!T.allocate(size_t n) 'n' exceeds maximum supported size");
|
|
|
|
static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof);
|
|
else
|
|
return cast(T*)__cpp_new(count * T.sizeof);
|
|
}
|
|
///
|
|
void deallocate(T* ptr, size_t count) @nogc
|
|
{
|
|
static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
|
{
|
|
static if (__cpp_sized_deallocation)
|
|
return __cpp_delete_size_aligned(cast(void*)ptr, count * T.sizeof, cast(align_val_t)T.alignof);
|
|
else
|
|
return __cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof);
|
|
}
|
|
else static if (__cpp_sized_deallocation)
|
|
return __cpp_delete_size(cast(void*)ptr, count * T.sizeof);
|
|
else
|
|
return __cpp_delete(cast(void*)ptr);
|
|
}
|
|
|
|
///
|
|
enum size_t max_size = size_t.max / T.sizeof;
|
|
}
|
|
else
|
|
{
|
|
static assert(false, "C++ runtime not supported");
|
|
}
|
|
}
|
|
|
|
///
|
|
extern(C++, (StdNamespace))
|
|
struct allocator_traits(Alloc)
|
|
{
|
|
import core.internal.traits : isTrue;
|
|
|
|
///
|
|
alias allocator_type = Alloc;
|
|
///
|
|
alias value_type = allocator_type.value_type;
|
|
///
|
|
alias size_type = allocator_type.size_type;
|
|
///
|
|
alias difference_type = allocator_type.difference_type;
|
|
///
|
|
alias pointer = allocator_type.pointer;
|
|
|
|
///
|
|
enum propagate_on_container_copy_assignment = isTrue!(allocator_type, "propagate_on_container_copy_assignment");
|
|
///
|
|
enum propagate_on_container_move_assignment = isTrue!(allocator_type, "propagate_on_container_move_assignment");
|
|
///
|
|
enum propagate_on_container_swap = isTrue!(allocator_type, "propagate_on_container_swap");
|
|
///
|
|
enum is_always_equal = isTrue!(allocator_type, "is_always_equal");
|
|
|
|
///
|
|
template rebind_alloc(U)
|
|
{
|
|
static if (__traits(hasMember, allocator_type, "rebind"))
|
|
alias rebind_alloc = allocator_type.rebind!U;
|
|
else
|
|
alias rebind_alloc = allocator_type!U;
|
|
}
|
|
///
|
|
alias rebind_traits(U) = allocator_traits!(rebind_alloc!U);
|
|
|
|
///
|
|
static size_type max_size()(auto ref allocator_type a)
|
|
{
|
|
static if (__traits(hasMember, allocator_type, "max_size"))
|
|
return a.max_size();
|
|
else
|
|
return size_type.max / value_type.sizeof;
|
|
}
|
|
|
|
///
|
|
static allocator_type select_on_container_copy_construction()(auto ref allocator_type a)
|
|
{
|
|
static if (__traits(hasMember, allocator_type, "select_on_container_copy_construction"))
|
|
return a.select_on_container_copy_construction();
|
|
else
|
|
return a;
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
// MSVC has some bonus complexity!
|
|
version (CppRuntime_Microsoft)
|
|
{
|
|
// some versions of VS require a `* const` pointer mangling hack
|
|
// we need a way to supply the target VS version to the compile
|
|
version = NeedsMangleHack;
|
|
|
|
version (X86)
|
|
version = INTEL_ARCH;
|
|
version (X86_64)
|
|
version = INTEL_ARCH;
|
|
|
|
// HACK: should we guess _DEBUG for `debug` builds?
|
|
version (_DEBUG)
|
|
enum _DEBUG = true;
|
|
else version (NDEBUG)
|
|
enum _DEBUG = false;
|
|
else
|
|
{
|
|
import core.stdcpp.xutility : __CXXLIB__;
|
|
enum _DEBUG = __CXXLIB__.length && 'd' == __CXXLIB__[$-1]; // libcmtd, msvcrtd
|
|
}
|
|
|
|
enum _New_alignof(T) = T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__ ? T.alignof : __STDCPP_DEFAULT_NEW_ALIGNMENT__;
|
|
|
|
version (INTEL_ARCH)
|
|
{
|
|
enum size_t _Big_allocation_threshold = 4096;
|
|
enum size_t _Big_allocation_alignment = 32;
|
|
|
|
static assert(2 * (void*).sizeof <= _Big_allocation_alignment, "Big allocation alignment should at least match vector register alignment");
|
|
static assert((v => v != 0 && (v & (v - 1)) == 0)(_Big_allocation_alignment), "Big allocation alignment must be a power of two");
|
|
static assert(size_t.sizeof == (void*).sizeof, "uintptr_t is not the same size as size_t");
|
|
|
|
// NOTE: this must track `_DEBUG` macro used in C++...
|
|
static if (_DEBUG)
|
|
enum size_t _Non_user_size = 2 * (void*).sizeof + _Big_allocation_alignment - 1;
|
|
else
|
|
enum size_t _Non_user_size = (void*).sizeof + _Big_allocation_alignment - 1;
|
|
|
|
version (Win64)
|
|
enum size_t _Big_allocation_sentinel = 0xFAFAFAFAFAFAFAFA;
|
|
else
|
|
enum size_t _Big_allocation_sentinel = 0xFAFAFAFA;
|
|
|
|
extern(D) // Template so it gets compiled according to _DEBUG.
|
|
void* _Allocate_manually_vector_aligned()(const size_t _Bytes) @nogc
|
|
{
|
|
size_t _Block_size = _Non_user_size + _Bytes;
|
|
if (_Block_size <= _Bytes)
|
|
_Block_size = size_t.max;
|
|
|
|
const size_t _Ptr_container = cast(size_t)__cpp_new(_Block_size);
|
|
if (!(_Ptr_container != 0))
|
|
assert(false, "invalid argument");
|
|
void* _Ptr = cast(void*)((_Ptr_container + _Non_user_size) & ~(_Big_allocation_alignment - 1));
|
|
(cast(size_t*)_Ptr)[-1] = _Ptr_container;
|
|
|
|
static if (_DEBUG)
|
|
(cast(size_t*)_Ptr)[-2] = _Big_allocation_sentinel;
|
|
return (_Ptr);
|
|
}
|
|
|
|
extern(D) // Template so it gets compiled according to _DEBUG.
|
|
void _Adjust_manually_vector_aligned()(ref void* _Ptr, ref size_t _Bytes) pure nothrow @nogc
|
|
{
|
|
_Bytes += _Non_user_size;
|
|
|
|
const size_t* _Ptr_user = cast(size_t*)_Ptr;
|
|
const size_t _Ptr_container = _Ptr_user[-1];
|
|
|
|
// If the following asserts, it likely means that we are performing
|
|
// an aligned delete on memory coming from an unaligned allocation.
|
|
static if (_DEBUG)
|
|
assert(_Ptr_user[-2] == _Big_allocation_sentinel, "invalid argument");
|
|
|
|
// Extra paranoia on aligned allocation/deallocation; ensure _Ptr_container is
|
|
// in range [_Min_back_shift, _Non_user_size]
|
|
static if (_DEBUG)
|
|
enum size_t _Min_back_shift = 2 * (void*).sizeof;
|
|
else
|
|
enum size_t _Min_back_shift = (void*).sizeof;
|
|
|
|
const size_t _Back_shift = cast(size_t)_Ptr - _Ptr_container;
|
|
if (!(_Back_shift >= _Min_back_shift && _Back_shift <= _Non_user_size))
|
|
assert(false, "invalid argument");
|
|
_Ptr = cast(void*)_Ptr_container;
|
|
}
|
|
}
|
|
}
|
|
version (CppRuntime_Clang)
|
|
{
|
|
// Helper for container swap
|
|
package(core.stdcpp) void __swap_allocator(Alloc)(ref Alloc __a1, ref Alloc __a2)
|
|
{
|
|
import core.internal.lifetime : swap;
|
|
|
|
static if (allocator_traits!Alloc.propagate_on_container_swap)
|
|
swap(__a1, __a2);
|
|
}
|
|
}
|