1 /++ 2 + D interface to the Boehm-Demers-Weiser Garbage Collector (BDWGC). 3 + Provides a structured allocator API for GC-managed memory with thread support. 4 + 5 + Note: All allocations, including aligned ones, are GC-managed via BDWGC. 6 + Thread registration is required for multi-threaded applications when GCThreads is enabled. 7 +/ 8 module bdwgc; 9 10 version (D_BetterC) 11 { 12 version (LDC) 13 { 14 pragma(LDC_no_moduleinfo); 15 pragma(LDC_no_typeinfo); 16 } 17 } 18 19 /// BDWGC C bindings 20 public import c.gc; // @system 21 import std.algorithm.comparison : max; 22 import std.experimental.allocator : IAllocator; 23 24 // Declare missing BDWGC thread functions 25 version (GCThreads) 26 { 27 extern (C) @nogc nothrow: 28 int GC_thread_is_registered(); // Returns non-zero if thread is registered 29 void GC_register_my_thread(); // Registers the current thread 30 void GC_unregister_my_thread(); // Unregisters the current thread 31 version (Posix) void GC_allow_register_threads(); // Enables dynamic thread registration 32 } 33 34 /++ 35 + Checks if alignment is valid: a power of 2 and at least pointer size. 36 +/ 37 bool isGoodDynamicAlignment(uint x) @nogc nothrow pure 38 { 39 return x >= (void*).sizeof && (x & (x - 1)) == 0; 40 } 41 42 version (Windows) 43 { 44 private import core.stdc.stdio : printf; 45 46 alias GC_printf = printf; /// Alias for Windows printf 47 } 48 else 49 { 50 /// Formatted output for GC logging 51 pragma(printf) 52 extern (C) void GC_printf(const(char)* format, ...) @trusted @nogc nothrow; 53 } 54 55 /++ 56 + Manage BDWGC thread registration. 57 + Use ThreadGuard.create() to instantiate and register the current thread. 58 + Unregisters the thread on destruction. No-op if GCThreads is disabled. 59 +/ 60 struct ThreadGuard 61 { 62 @nogc nothrow: 63 this(this) @disable; // Prevent copying 64 private bool isRegistered; // Track registration state 65 66 /// Factory function to create and register a ThreadGuard 67 @trusted static ThreadGuard create() 68 { 69 ThreadGuard guard; 70 version (GCThreads) 71 { 72 if (!GC_thread_is_registered()) 73 { 74 debug 75 GC_printf("Registering thread\n"); 76 GC_register_my_thread(); 77 guard.isRegistered = true; 78 } 79 } 80 return guard; 81 } 82 83 /// Unregisters the thread if registered 84 @trusted ~this() 85 { 86 version (GCThreads) 87 { 88 if (isRegistered && GC_thread_is_registered()) 89 { 90 debug 91 GC_printf("Unregistering thread\n"); 92 GC_unregister_my_thread(); 93 } 94 } 95 } 96 } 97 98 /++ 99 + Allocator for BDWGC-managed memory, implementing IAllocator. 100 + Thread-safe and compatible with `-betterC`. 101 + Requires thread registration for multi-threaded use when GCThreads is enabled. 102 +/ 103 struct BoehmAllocator 104 { 105 version (StdUnittest) @system unittest 106 { 107 extern (C) void testAllocator(alias alloc)(); // Declare testAllocator 108 testAllocator!(() => BoehmAllocator.instance)(); 109 } 110 111 /// Alignment ensures proper alignment for D data types 112 enum uint alignment = max(double.alignof, real.alignof); 113 114 /// One-time initialization of BDWGC with thread support 115 shared static this() @nogc nothrow 116 { 117 debug 118 GC_printf("Initializing BDWGC\n"); 119 GC_init(); 120 version (GCThreads) 121 { 122 // Enable thread support 123 version (Posix) 124 GC_allow_register_threads(); 125 } 126 } 127 128 /// Allocates memory of specified size, returns null if allocation fails 129 @trusted @nogc nothrow 130 void[] allocate(size_t bytes) shared 131 { 132 if (!bytes) 133 return null; 134 auto p = GC_MALLOC(bytes); 135 return p ? p[0 .. bytes] : null; 136 } 137 138 /// Allocates aligned memory using GC_memalign, returns null if allocation fails 139 @trusted @nogc nothrow 140 void[] alignedAllocate(size_t bytes, uint a) shared 141 { 142 if (!bytes || !a.isGoodDynamicAlignment) 143 return null; 144 auto p = GC_memalign(a, bytes); 145 return p ? p[0 .. bytes] : null; 146 } 147 148 /// Deallocates memory, safe for null buffers 149 @system @nogc nothrow 150 bool deallocate(void[] b) shared 151 { 152 if (b.ptr) 153 GC_FREE(b.ptr); 154 return true; 155 } 156 157 /// Reallocates memory to new size, handles zero-size deallocation 158 @system @nogc nothrow 159 bool reallocate(ref void[] b, size_t newSize) shared 160 { 161 if (!newSize) 162 { 163 deallocate(b); 164 b = null; 165 return true; 166 } 167 auto p = GC_REALLOC(b.ptr, newSize); 168 if (!p) 169 return false; 170 b = p[0 .. newSize]; 171 return true; 172 } 173 174 /// Allocates zero-initialized memory 175 @trusted @nogc nothrow 176 void[] allocateZeroed(size_t bytes) shared 177 { 178 if (!bytes) 179 return null; 180 auto p = GC_MALLOC_ATOMIC(bytes); 181 if (!p) 182 return null; 183 import core.stdc.string : memset; 184 185 memset(p, 0, bytes); 186 return p[0 .. bytes]; 187 } 188 189 /// Enables incremental garbage collection 190 @trusted @nogc nothrow 191 void enableIncremental() shared 192 { 193 GC_enable_incremental(); 194 } 195 196 /// Disables garbage collection 197 @trusted @nogc nothrow 198 void disable() shared 199 { 200 GC_disable(); 201 } 202 203 /// Triggers garbage collection 204 @trusted @nogc nothrow 205 void collect() shared 206 { 207 GC_gcollect(); 208 } 209 210 /// Checks if pointer is GC-managed 211 @trusted @nogc nothrow 212 bool isHeapPtr(const void* ptr) shared const 213 { 214 return GC_is_heap_ptr(cast(void*) ptr) != 0; 215 } 216 217 /// Checks if the allocator owns the memory block 218 @trusted @nogc nothrow 219 bool owns(void[] b) shared const 220 { 221 return b.ptr && isHeapPtr(b.ptr); 222 } 223 224 /// Suggests a good allocation size 225 @trusted @nogc nothrow 226 size_t goodAllocSize(size_t n) shared const 227 { 228 if (n == 0) 229 return 0; 230 // Round up to the next multiple of alignment 231 return ((n + alignment - 1) / alignment) * alignment; 232 } 233 234 /// Global thread-safe instance 235 static shared BoehmAllocator instance; 236 237 // IAllocator interface compliance 238 alias allocate this; 239 } 240 241 /** 242 * Unit tests 243 */ 244 version (unittest) 245 { 246 import std.experimental.allocator : makeArray; 247 248 @("Basic allocation and deallocation") 249 @nogc @system nothrow unittest 250 { 251 auto guard = ThreadGuard.create(); 252 auto buffer = BoehmAllocator.instance.allocate(1024 * 1024 * 4); 253 scope (exit) 254 BoehmAllocator.instance.deallocate(buffer); 255 assert(buffer !is null); 256 assert(BoehmAllocator.instance.isHeapPtr(buffer.ptr)); 257 assert(BoehmAllocator.instance.owns(buffer)); 258 } 259 260 @("Aligned allocation") 261 @nogc @system nothrow unittest 262 { 263 auto guard = ThreadGuard.create(); 264 auto buffer = BoehmAllocator.instance.alignedAllocate(1024, 128); 265 scope (exit) 266 BoehmAllocator.instance.deallocate(buffer); 267 assert(buffer !is null); 268 assert((cast(size_t) buffer.ptr) % 128 == 0); 269 } 270 271 @("Reallocation and zeroed allocation") 272 @nogc @system nothrow unittest 273 { 274 auto guard = ThreadGuard.create(); 275 void[] b = BoehmAllocator.instance.allocate(16); 276 assert(b !is null, "Allocation failed"); 277 (cast(ubyte[]) b)[] = ubyte(1); 278 // Debug: Print buffer contents before reallocation 279 debug 280 { 281 GC_printf("Before realloc: "); 282 foreach (i; 0 .. 16) 283 GC_printf("%02x ", (cast(ubyte[]) b)[i]); 284 GC_printf("\n"); 285 } 286 assert(BoehmAllocator.instance.reallocate(b, 32), "Reallocation failed"); 287 // Debug: Print buffer contents after reallocation 288 debug 289 { 290 GC_printf("After realloc: "); 291 foreach (i; 0 .. 16) 292 GC_printf("%02x ", (cast(ubyte[]) b)[i]); 293 GC_printf("\n"); 294 } 295 ubyte[16] expected = 1; 296 // Manual comparison to avoid issues 297 bool isEqual = true; 298 for (size_t i = 0; i < 16; i++) 299 if ((cast(ubyte[]) b)[i] != 1) 300 { 301 isEqual = false; 302 break; 303 } 304 assert(isEqual, "Reallocated buffer contents incorrect"); 305 BoehmAllocator.instance.deallocate(b); 306 307 auto zeroed = BoehmAllocator.instance.allocateZeroed(16); 308 assert(zeroed !is null, "Zeroed allocation failed"); 309 ubyte[16] zeroExpected = 0; 310 assert((cast(ubyte[]) zeroed)[] == zeroExpected, "Zeroed buffer not zero"); 311 BoehmAllocator.instance.deallocate(zeroed); 312 } 313 314 @("Incremental GC and collection") 315 @nogc @system nothrow unittest 316 { 317 auto guard = ThreadGuard.create(); 318 BoehmAllocator.instance.enableIncremental(); 319 auto b = BoehmAllocator.instance.allocate(1024); 320 assert(b !is null); 321 BoehmAllocator.instance.collect(); 322 BoehmAllocator.instance.disable(); 323 BoehmAllocator.instance.deallocate(b); 324 } 325 326 @("Allocator interface compliance") 327 @nogc @system nothrow unittest 328 { 329 auto guard = ThreadGuard.create(); 330 static void test(A)() 331 { 332 int* p = cast(int*) A.instance.allocate(int.sizeof); 333 scope (exit) 334 A.instance.deallocate(p[0 .. int.sizeof]); 335 *p = 42; 336 assert(*p == 42); 337 } 338 339 test!BoehmAllocator(); 340 } 341 342 @("Thread registration") 343 @nogc @system nothrow unittest 344 { 345 version (GCThreads) 346 { 347 assert(!GC_thread_is_registered()); 348 { 349 auto guard = ThreadGuard.create(); 350 assert(GC_thread_is_registered()); 351 auto buffer = BoehmAllocator.instance.allocate(1024); 352 assert(buffer !is null); 353 BoehmAllocator.instance.deallocate(buffer); 354 } 355 assert(!GC_thread_is_registered()); 356 } 357 else 358 { 359 auto guard = ThreadGuard.create(); 360 auto buffer = BoehmAllocator.instance.allocate(1024); 361 assert(buffer !is null); 362 BoehmAllocator.instance.deallocate(buffer); 363 } 364 } 365 366 @("Aligned allocation (cross-platform)") 367 @nogc @system nothrow unittest 368 { 369 auto guard = ThreadGuard.create(); 370 void[] b = BoehmAllocator.instance.alignedAllocate(16, 32); 371 (cast(ubyte[]) b)[] = ubyte(1); 372 ubyte[16] expected = 1; 373 assert((cast(ubyte[]) b)[] == expected); 374 BoehmAllocator.instance.deallocate(b); 375 } 376 377 @("IAllocator compliance") 378 @nogc @system nothrow unittest 379 { 380 auto guard = ThreadGuard.create(); 381 char*[] names = makeArray!(char*)(BoehmAllocator.instance, 3); 382 assert(names.length == 3); 383 assert(names.ptr); 384 BoehmAllocator.instance.deallocate(names); 385 } 386 }