From 18b43ebeb5b56d010ae18c5d6aff7b366d547845 Mon Sep 17 00:00:00 2001 From: Atila Neves Date: Mon, 17 Aug 2026 14:00:32 +0200 Subject: [PATCH 1/2] Add GCHeapMallocator --- .../allocator/building_blocks/package.d | 3 + std/experimental/allocator/gc_allocator.d | 134 +++++++++++++++++- 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/std/experimental/allocator/building_blocks/package.d b/std/experimental/allocator/building_blocks/package.d index 521d7ed0a47..874ccdec57a 100644 --- a/std/experimental/allocator/building_blocks/package.d +++ b/std/experimental/allocator/building_blocks/package.d @@ -219,6 +219,9 @@ $(TR $(TDC3 GCAllocator, gc_allocator) $(TD The system-provided garbage-collecto This should be the default fallback allocator tapping into system memory. It offers manual `free` and dutifully collects litter.)) +$(TR $(TDC3 GCHeapMallocator, gc_allocator) $(TD Allocates GC-scanned memory +that the caller releases manually.)) + $(TR $(TDC3 Mallocator, mallocator) $(TD The C heap _allocator, a.k.a. $(D malloc)/`realloc`/`free`. Use sparingly and only for code that is unlikely to leak.)) diff --git a/std/experimental/allocator/gc_allocator.d b/std/experimental/allocator/gc_allocator.d index d356c737633..dc99132b8a2 100644 --- a/std/experimental/allocator/gc_allocator.d +++ b/std/experimental/allocator/gc_allocator.d @@ -1,6 +1,6 @@ // Written in the D programming language. /** -D's built-in garbage-collected allocator. +Allocators that draw from the D runtime's garbage-collected heap. Source: $(PHOBOSSRC std/experimental/allocator/_gc_allocator.d) */ @@ -197,3 +197,135 @@ pure nothrow @safe unittest void *badPtr = (() @trusted => cast(void*)(0xdeadbeef))(); assert(GCAllocator.instance.resolveInternalPointer(badPtr, result) == Ternary.no); } + +/** +Allocates from the D runtime's garbage-collected heap, but frees memory +manually using $(REF GC.free, core,memory) rather than leaving it for the GC to +collect. +*/ +struct GCHeapMallocator +{ + import std.typecons : Ternary; + version (StdUnittest) @system unittest { testAllocator!(() => GCHeapMallocator.instance); } + + private GCAllocator impl; + + /** + The alignment is a static constant equal to `platformAlignment`, which + ensures proper alignment for any D data type. + */ + enum uint alignment = impl.alignment; + + /** + Standard allocator methods per the semantics defined above. The $(D + deallocate) and `reallocate` methods are `@system` because they may + move memory around, leaving dangling pointers in user code. + */ + pure nothrow @trusted void[] allocate(size_t bytes) shared const + { + return impl.allocate(bytes); + } + + /// Ditto + pure nothrow @trusted bool expand(ref void[] b, size_t delta) shared const + { + return impl.expand(b, delta); + } + + /// Ditto + pure nothrow @system bool reallocate(ref void[] b, size_t newSize) shared const + { + return impl.reallocate(b, newSize); + } + + /// Ditto + pure nothrow @trusted @nogc + Ternary resolveInternalPointer(const void* p, ref void[] result) shared const + { + return impl.resolveInternalPointer(p, result); + } + + /// Ditto + pure nothrow @system @nogc + bool deallocate(void[] b) shared const + { + return impl.deallocate(b); + } + + /// Ditto + pure nothrow @safe @nogc + size_t goodAllocSize(size_t n) shared const + { + return impl.goodAllocSize(n); + } + + package pure nothrow @trusted void[] allocateZeroed()(size_t bytes) shared const + { + return impl.allocateZeroed(bytes); + } + + /** + Returns the global instance of this allocator type. The garbage-collected + allocator is thread-safe, therefore all of its methods and `instance` itself + are `shared`. + */ + static shared const GCHeapMallocator instance; + + // Leave it undocumented for now. + nothrow @trusted void collect() shared const + { + impl.collect(); + } +} + +/// +pure @system unittest +{ + auto buffer = GCHeapMallocator.instance.allocate(1024 * 1024 * 4); + scope(exit) GCHeapMallocator.instance.deallocate(buffer); +} + +pure @safe unittest +{ + auto b = GCHeapMallocator.instance.allocate(10_000); + assert(GCHeapMallocator.instance.expand(b, 1)); +} + +pure @system unittest +{ + import core.memory : GC; + import std.typecons : Ternary; + + assert((() nothrow @safe @nogc => GCHeapMallocator.instance.goodAllocSize(1))() == 16); + for (size_t s = 16; s <= 8192; s *= 2) + { + assert((() nothrow @safe @nogc => GCHeapMallocator.instance.goodAllocSize(s))() == s); + assert((() nothrow @safe @nogc => GCHeapMallocator.instance.goodAllocSize(s - (s / 2) + 1))() == s); + + auto buffer = GCHeapMallocator.instance.allocate(s); + scope(exit) () nothrow @nogc { GCHeapMallocator.instance.deallocate(buffer); }(); + + void[] p; + assert((() nothrow @safe => GCHeapMallocator.instance.resolveInternalPointer(null, p))() == Ternary.no); + assert((() nothrow @safe => GCHeapMallocator.instance.resolveInternalPointer(&buffer[0], p))() == Ternary.yes); + assert(p.ptr is buffer.ptr && p.length >= buffer.length); + + assert(GC.sizeOf(buffer.ptr) == s); + } + + assert((() nothrow @safe @nogc => GCHeapMallocator.instance.goodAllocSize(4096 * 4 + 1))() == 4096 * 5); +} + +pure nothrow @safe unittest +{ + import std.typecons : Ternary; + + void[] buffer = GCHeapMallocator.instance.allocate(42); + void[] result; + Ternary found = GCHeapMallocator.instance.resolveInternalPointer(&buffer[0], result); + + assert(found == Ternary.yes && &result[0] == &buffer[0] && result.length >= buffer.length); + assert(GCHeapMallocator.instance.resolveInternalPointer(null, result) == Ternary.no); + void *badPtr = (() @trusted => cast(void*)(0xdeadbeef))(); + assert(GCHeapMallocator.instance.resolveInternalPointer(badPtr, result) == Ternary.no); +} From a87506decd288a2443f2700ad41fd1400e0b248a Mon Sep 17 00:00:00 2001 From: Atila Neves Date: Wed, 19 Aug 2026 08:56:20 +0200 Subject: [PATCH 2/2] changelog: add entry for GCHeapMallocator Co-Authored-By: Claude Opus 5 (1M context) --- changelog/gc_heap_mallocator.dd | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 changelog/gc_heap_mallocator.dd diff --git a/changelog/gc_heap_mallocator.dd b/changelog/gc_heap_mallocator.dd new file mode 100644 index 00000000000..fcd8e68cd96 --- /dev/null +++ b/changelog/gc_heap_mallocator.dd @@ -0,0 +1,17 @@ +New `GCHeapMallocator` allocator in `std.experimental.allocator` + +$(REF_ALTTEXT `GCHeapMallocator`, GCHeapMallocator, std, experimental, allocator, gc_allocator) +allocates from the D runtime's garbage-collected heap, but expects the memory to +be released manually via `deallocate` instead of leaving it for the collector. +The memory is still scanned for pointers by the GC. + +It offers the same allocation, reallocation and deallocation behaviour that +$(REF_ALTTEXT `GCAllocator`, GCAllocator, std, experimental, allocator, gc_allocator) +has today, under a name that says so explicitly. + +------- +import std.experimental.allocator.gc_allocator : GCHeapMallocator; + +auto buffer = GCHeapMallocator.instance.allocate(1024 * 1024 * 4); +scope(exit) GCHeapMallocator.instance.deallocate(buffer); +-------