1 /** 2 * D binding to C++ std::allocator. 3 * 4 * Copyright: Copyright (c) 2019 D Language Foundation 5 * License: Distributed under the 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 7 * (See accompanying file LICENSE) 8 * Authors: Manu Evans 9 * Source: $(DRUNTIMESRC core/stdcpp/allocator.d) 10 */ 11 12 module core.stdcpp.allocator; 13 14 // LDC: empty module for unsupported C++ runtimes 15 version (CppRuntime_Microsoft) version = Supported; 16 else version (CppRuntime_Gcc) version = Supported; 17 else version (CppRuntime_Clang) version = Supported; 18 version (Supported): 19 20 import core.stdcpp.new_; 21 import core.stdcpp.xutility : StdNamespace, __cpp_sized_deallocation, __cpp_aligned_new; 22 23 extern(C++, (StdNamespace)): 24 25 /** 26 * Allocators are classes that define memory models to be used by some parts of 27 * the C++ Standard Library, and most specifically, by STL containers. 28 */ 29 extern(C++, class) 30 struct allocator(T) 31 { 32 static assert(!is(T == const), "The C++ Standard forbids containers of const elements because allocator!(const T) is ill-formed."); 33 static assert(!is(T == immutable), "immutable is not representable in C++"); 34 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."); 35 extern(D): 36 37 /// 38 this(U)(ref allocator!U) {} 39 40 /// 41 alias size_type = size_t; 42 /// 43 alias difference_type = ptrdiff_t; 44 /// 45 alias pointer = T*; 46 /// 47 alias value_type = T; 48 49 /// 50 enum propagate_on_container_move_assignment = true; 51 /// 52 enum is_always_equal = true; 53 54 /// 55 alias rebind(U) = allocator!U; 56 57 version (CppRuntime_Microsoft) 58 { 59 import core.stdcpp.xutility : _MSC_VER; 60 61 /// 62 T* allocate(size_t count) @nogc 63 { 64 static if (_MSC_VER <= 1800) 65 { 66 import core.stdcpp.xutility : _Xbad_alloc; 67 if (count == 0) 68 return null; 69 void* mem; 70 if ((size_t.max / T.sizeof < count) || (mem = __cpp_new(count * T.sizeof)) is null) 71 _Xbad_alloc(); 72 return cast(T*)mem; 73 } 74 else 75 { 76 enum _Align = _New_alignof!T; 77 78 static size_t _Get_size_of_n(T)(const size_t _Count) 79 { 80 static if (T.sizeof == 1) 81 return _Count; 82 else 83 { 84 enum size_t _Max_possible = size_t.max / T.sizeof; 85 return _Max_possible < _Count ? size_t.max : _Count * T.sizeof; 86 } 87 } 88 89 const size_t _Bytes = _Get_size_of_n!T(count); 90 if (_Bytes == 0) 91 return null; 92 93 static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) 94 { 95 version (INTEL_ARCH) 96 { 97 if (_Bytes >= _Big_allocation_threshold) 98 return cast(T*)_Allocate_manually_vector_aligned(_Bytes); 99 } 100 return cast(T*)__cpp_new(_Bytes); 101 } 102 else 103 { 104 size_t _Passed_align = _Align; 105 version (INTEL_ARCH) 106 { 107 if (_Bytes >= _Big_allocation_threshold) 108 _Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align; 109 } 110 return cast(T*)__cpp_new_aligned(_Bytes, cast(align_val_t)_Passed_align); 111 } 112 } 113 } 114 /// 115 void deallocate(T* ptr, size_t count) @nogc 116 { 117 static if (_MSC_VER <= 1800) 118 { 119 __cpp_delete(ptr); 120 } 121 else 122 { 123 // this is observed from VS2017 124 void* _Ptr = ptr; 125 size_t _Bytes = T.sizeof * count; 126 127 enum _Align = _New_alignof!T; 128 static if (!__cpp_aligned_new || _Align <= __STDCPP_DEFAULT_NEW_ALIGNMENT__) 129 { 130 version (INTEL_ARCH) 131 { 132 if (_Bytes >= _Big_allocation_threshold) 133 _Adjust_manually_vector_aligned(_Ptr, _Bytes); 134 } 135 static if (_MSC_VER <= 1900) 136 __cpp_delete(ptr); 137 else 138 __cpp_delete_size(_Ptr, _Bytes); 139 } 140 else 141 { 142 size_t _Passed_align = _Align; 143 version (INTEL_ARCH) 144 { 145 if (_Bytes >= _Big_allocation_threshold) 146 _Passed_align = _Align < _Big_allocation_alignment ? _Big_allocation_alignment : _Align; 147 } 148 __cpp_delete_size_aligned(_Ptr, _Bytes, cast(align_val_t)_Passed_align); 149 } 150 } 151 } 152 153 /// 154 enum size_t max_size = size_t.max / T.sizeof; 155 } 156 else version (CppRuntime_Gcc) 157 { 158 /// 159 T* allocate(size_t count, const(void)* = null) @nogc 160 { 161 // if (count > max_size) 162 // std::__throw_bad_alloc(); 163 164 static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 165 return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof); 166 else 167 return cast(T*)__cpp_new(count * T.sizeof); 168 } 169 /// 170 void deallocate(T* ptr, size_t count) @nogc 171 { 172 // NOTE: GCC doesn't seem to use the sized delete when it's available... 173 174 static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 175 __cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof); 176 else 177 __cpp_delete(cast(void*)ptr); 178 } 179 180 /// 181 enum size_t max_size = (ptrdiff_t.max < size_t.max ? cast(size_t)ptrdiff_t.max : size_t.max) / T.sizeof; 182 } 183 else version (CppRuntime_Clang) 184 { 185 /// 186 T* allocate(size_t count, const(void)* = null) @nogc 187 { 188 // if (count > max_size) 189 // __throw_length_error("allocator!T.allocate(size_t n) 'n' exceeds maximum supported size"); 190 191 static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 192 return cast(T*)__cpp_new_aligned(count * T.sizeof, cast(align_val_t)T.alignof); 193 else 194 return cast(T*)__cpp_new(count * T.sizeof); 195 } 196 /// 197 void deallocate(T* ptr, size_t count) @nogc 198 { 199 static if (__cpp_aligned_new && T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__) 200 { 201 static if (__cpp_sized_deallocation) 202 return __cpp_delete_size_aligned(cast(void*)ptr, count * T.sizeof, cast(align_val_t)T.alignof); 203 else 204 return __cpp_delete_aligned(cast(void*)ptr, cast(align_val_t)T.alignof); 205 } 206 else static if (__cpp_sized_deallocation) 207 return __cpp_delete_size(cast(void*)ptr, count * T.sizeof); 208 else 209 return __cpp_delete(cast(void*)ptr); 210 } 211 212 /// 213 enum size_t max_size = size_t.max / T.sizeof; 214 } 215 else 216 { 217 static assert(false, "C++ runtime not supported"); 218 } 219 } 220 221 /// 222 extern(C++, (StdNamespace)) 223 struct allocator_traits(Alloc) 224 { 225 import core.internal.traits : isTrue; 226 227 /// 228 alias allocator_type = Alloc; 229 /// 230 alias value_type = allocator_type.value_type; 231 /// 232 alias size_type = allocator_type.size_type; 233 /// 234 alias difference_type = allocator_type.difference_type; 235 /// 236 alias pointer = allocator_type.pointer; 237 238 /// 239 enum propagate_on_container_copy_assignment = isTrue!(allocator_type, "propagate_on_container_copy_assignment"); 240 /// 241 enum propagate_on_container_move_assignment = isTrue!(allocator_type, "propagate_on_container_move_assignment"); 242 /// 243 enum propagate_on_container_swap = isTrue!(allocator_type, "propagate_on_container_swap"); 244 /// 245 enum is_always_equal = isTrue!(allocator_type, "is_always_equal"); 246 247 /// 248 template rebind_alloc(U) 249 { 250 static if (__traits(hasMember, allocator_type, "rebind")) 251 alias rebind_alloc = allocator_type.rebind!U; 252 else 253 alias rebind_alloc = allocator_type!U; 254 } 255 /// 256 alias rebind_traits(U) = allocator_traits!(rebind_alloc!U); 257 258 /// 259 static size_type max_size()(auto ref allocator_type a) 260 { 261 static if (__traits(hasMember, allocator_type, "max_size")) 262 return a.max_size(); 263 else 264 return size_type.max / value_type.sizeof; 265 } 266 267 /// 268 static allocator_type select_on_container_copy_construction()(auto ref allocator_type a) 269 { 270 static if (__traits(hasMember, allocator_type, "select_on_container_copy_construction")) 271 return a.select_on_container_copy_construction(); 272 else 273 return a; 274 } 275 } 276 277 private: 278 279 // MSVC has some bonus complexity! 280 version (CppRuntime_Microsoft) 281 { 282 // some versions of VS require a `* const` pointer mangling hack 283 // we need a way to supply the target VS version to the compile 284 version = NeedsMangleHack; 285 286 version (X86) 287 version = INTEL_ARCH; 288 version (X86_64) 289 version = INTEL_ARCH; 290 291 // HACK: should we guess _DEBUG for `debug` builds? 292 version (_DEBUG) 293 enum _DEBUG = true; 294 else version (NDEBUG) 295 enum _DEBUG = false; 296 else 297 { 298 import core.stdcpp.xutility : __CXXLIB__; 299 enum _DEBUG = __CXXLIB__.length && 'd' == __CXXLIB__[$-1]; // libcmtd, msvcrtd 300 } 301 302 enum _New_alignof(T) = T.alignof > __STDCPP_DEFAULT_NEW_ALIGNMENT__ ? T.alignof : __STDCPP_DEFAULT_NEW_ALIGNMENT__; 303 304 version (INTEL_ARCH) 305 { 306 enum size_t _Big_allocation_threshold = 4096; 307 enum size_t _Big_allocation_alignment = 32; 308 309 static assert(2 * (void*).sizeof <= _Big_allocation_alignment, "Big allocation alignment should at least match vector register alignment"); 310 static assert((v => v != 0 && (v & (v - 1)) == 0)(_Big_allocation_alignment), "Big allocation alignment must be a power of two"); 311 static assert(size_t.sizeof == (void*).sizeof, "uintptr_t is not the same size as size_t"); 312 313 // NOTE: this must track `_DEBUG` macro used in C++... 314 static if (_DEBUG) 315 enum size_t _Non_user_size = 2 * (void*).sizeof + _Big_allocation_alignment - 1; 316 else 317 enum size_t _Non_user_size = (void*).sizeof + _Big_allocation_alignment - 1; 318 319 version (Win64) 320 enum size_t _Big_allocation_sentinel = 0xFAFAFAFAFAFAFAFA; 321 else 322 enum size_t _Big_allocation_sentinel = 0xFAFAFAFA; 323 324 extern(D) // Template so it gets compiled according to _DEBUG. 325 void* _Allocate_manually_vector_aligned()(const size_t _Bytes) @nogc 326 { 327 size_t _Block_size = _Non_user_size + _Bytes; 328 if (_Block_size <= _Bytes) 329 _Block_size = size_t.max; 330 331 const size_t _Ptr_container = cast(size_t)__cpp_new(_Block_size); 332 if (!(_Ptr_container != 0)) 333 assert(false, "invalid argument"); 334 void* _Ptr = cast(void*)((_Ptr_container + _Non_user_size) & ~(_Big_allocation_alignment - 1)); 335 (cast(size_t*)_Ptr)[-1] = _Ptr_container; 336 337 static if (_DEBUG) 338 (cast(size_t*)_Ptr)[-2] = _Big_allocation_sentinel; 339 return (_Ptr); 340 } 341 342 extern(D) // Template so it gets compiled according to _DEBUG. 343 void _Adjust_manually_vector_aligned()(ref void* _Ptr, ref size_t _Bytes) pure nothrow @nogc 344 { 345 _Bytes += _Non_user_size; 346 347 const size_t* _Ptr_user = cast(size_t*)_Ptr; 348 const size_t _Ptr_container = _Ptr_user[-1]; 349 350 // If the following asserts, it likely means that we are performing 351 // an aligned delete on memory coming from an unaligned allocation. 352 static if (_DEBUG) 353 assert(_Ptr_user[-2] == _Big_allocation_sentinel, "invalid argument"); 354 355 // Extra paranoia on aligned allocation/deallocation; ensure _Ptr_container is 356 // in range [_Min_back_shift, _Non_user_size] 357 static if (_DEBUG) 358 enum size_t _Min_back_shift = 2 * (void*).sizeof; 359 else 360 enum size_t _Min_back_shift = (void*).sizeof; 361 362 const size_t _Back_shift = cast(size_t)_Ptr - _Ptr_container; 363 if (!(_Back_shift >= _Min_back_shift && _Back_shift <= _Non_user_size)) 364 assert(false, "invalid argument"); 365 _Ptr = cast(void*)_Ptr_container; 366 } 367 } 368 } 369 version (CppRuntime_Clang) 370 { 371 // Helper for container swap 372 package(core.stdcpp) void __swap_allocator(Alloc)(ref Alloc __a1, ref Alloc __a2) 373 { 374 import core.internal.lifetime : swap; 375 376 static if (allocator_traits!Alloc.propagate_on_container_swap) 377 swap(__a1, __a2); 378 } 379 }