| // Written in the D programming language. |
| /** |
| |
| High-level interface for allocators. Implements bundled allocation/creation |
| and destruction/deallocation of data including `struct`s and `class`es, |
| and also array primitives related to allocation. This module is the entry point |
| for both making use of allocators and for their documentation. |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(BOOKTABLE, |
| $(TR $(TH Category) $(TH Functions)) |
| $(TR $(TD Make) $(TD |
| $(LREF make) |
| $(LREF makeArray) |
| $(LREF makeMultidimensionalArray) |
| )) |
| $(TR $(TD Dispose) $(TD |
| $(LREF dispose) |
| $(LREF disposeMultidimensionalArray) |
| )) |
| $(TR $(TD Modify) $(TD |
| $(LREF expandArray) |
| $(LREF shrinkArray) |
| )) |
| $(TR $(TD Global) $(TD |
| $(LREF processAllocator) |
| $(LREF theAllocator) |
| )) |
| $(TR $(TD Class interface) $(TD |
| $(LREF allocatorObject) |
| $(LREF CAllocatorImpl) |
| $(LREF IAllocator) |
| )) |
| ) |
| |
| Synopsis: |
| --- |
| // Allocate an int, initialize it with 42 |
| int* p = theAllocator.make!int(42); |
| assert(*p == 42); |
| // Destroy and deallocate it |
| theAllocator.dispose(p); |
| |
| // Allocate using the global process allocator |
| p = processAllocator.make!int(100); |
| assert(*p == 100); |
| // Destroy and deallocate |
| processAllocator.dispose(p); |
| |
| // Create an array of 50 doubles initialized to -1.0 |
| double[] arr = theAllocator.makeArray!double(50, -1.0); |
| // Append two zeros to it |
| theAllocator.expandArray(arr, 2, 0.0); |
| // On second thought, take that back |
| theAllocator.shrinkArray(arr, 2); |
| // Destroy and deallocate |
| theAllocator.dispose(arr); |
| --- |
| |
| $(H2 Layered Structure) |
| |
| D's allocators have a layered structure in both implementation and documentation: |
| |
| $(OL |
| $(LI A high-level, dynamically-typed layer (described further down in this |
| module). It consists of an interface called $(LREF IAllocator), which concrete |
| allocators need to implement. The interface primitives themselves are oblivious |
| to the type of the objects being allocated; they only deal in `void[]`, by |
| necessity of the interface being dynamic (as opposed to type-parameterized). |
| Each thread has a current allocator it uses by default, which is a thread-local |
| variable $(LREF theAllocator) of type $(LREF IAllocator). The process has a |
| global allocator called $(LREF processAllocator), also of type $(LREF |
| IAllocator). When a new thread is created, $(LREF processAllocator) is copied |
| into $(LREF theAllocator). An application can change the objects to which these |
| references point. By default, at application startup, $(LREF processAllocator) |
| refers to an object that uses D's garbage collected heap. This layer also |
| include high-level functions such as $(LREF make) and $(LREF dispose) that |
| comfortably allocate/create and respectively destroy/deallocate objects. This |
| layer is all needed for most casual uses of allocation primitives.) |
| |
| $(LI A mid-level, statically-typed layer for assembling several allocators into |
| one. It uses properties of the type of the objects being created to route |
| allocation requests to possibly specialized allocators. This layer is relatively |
| thin and implemented and documented in the $(MREF |
| std,experimental,allocator,typed) module. It allows an interested user to e.g. |
| use different allocators for arrays versus fixed-sized objects, to the end of |
| better overall performance.) |
| |
| $(LI A low-level collection of highly generic $(I heap building blocks)$(MDASH) |
| Lego-like pieces that can be used to assemble application-specific allocators. |
| The real allocation smarts are occurring at this level. This layer is of |
| interest to advanced applications that want to configure their own allocators. |
| A good illustration of typical uses of these building blocks is module $(MREF |
| std,experimental,allocator,showcase) which defines a collection of frequently- |
| used preassembled allocator objects. The implementation and documentation entry |
| point is $(MREF std,experimental,allocator,building_blocks). By design, the |
| primitives of the static interface have the same signatures as the $(LREF |
| IAllocator) primitives but are for the most part optional and driven by static |
| introspection. The parameterized class $(LREF CAllocatorImpl) offers an |
| immediate and useful means to package a static low-level allocator into an |
| implementation of $(LREF IAllocator).) |
| |
| $(LI Core allocator objects that interface with D's garbage collected heap |
| ($(MREF std,experimental,allocator,gc_allocator)), the C `malloc` family |
| ($(MREF std,experimental,allocator,mallocator)), and the OS ($(MREF |
| std,experimental,allocator,mmap_allocator)). Most custom allocators would |
| ultimately obtain memory from one of these core allocators.) |
| ) |
| |
| $(H2 Idiomatic Use of `std.experimental.allocator`) |
| |
| As of this time, `std.experimental.allocator` is not integrated with D's |
| built-in operators that allocate memory, such as `new`, array literals, or |
| array concatenation operators. That means `std.experimental.allocator` is |
| opt-in$(MDASH)applications need to make explicit use of it. |
| |
| For casual creation and disposal of dynamically-allocated objects, use $(LREF |
| make), $(LREF dispose), and the array-specific functions $(LREF makeArray), |
| $(LREF expandArray), and $(LREF shrinkArray). These use by default D's garbage |
| collected heap, but open the application to better configuration options. These |
| primitives work either with `theAllocator` but also with any allocator obtained |
| by combining heap building blocks. For example: |
| |
| ---- |
| void fun(size_t n) |
| { |
| // Use the current allocator |
| int[] a1 = theAllocator.makeArray!int(n); |
| scope(exit) theAllocator.dispose(a1); |
| ... |
| } |
| ---- |
| |
| To experiment with alternative allocators, set $(LREF theAllocator) for the |
| current thread. For example, consider an application that allocates many 8-byte |
| objects. These are not well supported by the default allocator, so a |
| $(MREF_ALTTEXT free list allocator, |
| std,experimental,allocator,building_blocks,free_list) would be recommended. |
| To install one in `main`, the application would use: |
| |
| ---- |
| void main() |
| { |
| import std.experimental.allocator.building_blocks.free_list |
| : FreeList; |
| theAllocator = allocatorObject(FreeList!8()); |
| ... |
| } |
| ---- |
| |
| $(H3 Saving the `IAllocator` Reference For Later Use) |
| |
| As with any global resource, setting `theAllocator` and `processAllocator` |
| should not be done often and casually. In particular, allocating memory with |
| one allocator and deallocating with another causes undefined behavior. |
| Typically, these variables are set during application initialization phase and |
| last through the application. |
| |
| To avoid this, long-lived objects that need to perform allocations, |
| reallocations, and deallocations relatively often may want to store a reference |
| to the allocator object they use throughout their lifetime. Then, instead of |
| using `theAllocator` for internal allocation-related tasks, they'd use the |
| internally held reference. For example, consider a user-defined hash table: |
| |
| ---- |
| struct HashTable |
| { |
| private IAllocator allocator; |
| this(size_t buckets, IAllocator allocator = theAllocator) { |
| this.allocator = allocator; |
| ... |
| } |
| // Getter and setter |
| IAllocator allocator() { return allocator; } |
| void allocator(IAllocator a) { assert(empty); allocator = a; } |
| } |
| ---- |
| |
| Following initialization, the `HashTable` object would consistently use its |
| `allocator` object for acquiring memory. Furthermore, setting |
| `HashTable.allocator` to point to a different allocator should be legal but |
| only if the object is empty; otherwise, the object wouldn't be able to |
| deallocate its existing state. |
| |
| $(H3 Using Allocators without `IAllocator`) |
| |
| Allocators assembled from the heap building blocks don't need to go through |
| `IAllocator` to be usable. They have the same primitives as `IAllocator` and |
| they work with $(LREF make), $(LREF makeArray), $(LREF dispose) etc. So it |
| suffice to create allocator objects wherever fit and use them appropriately: |
| |
| ---- |
| void fun(size_t n) |
| { |
| // Use a stack-installed allocator for up to 64KB |
| StackFront!65536 myAllocator; |
| int[] a2 = myAllocator.makeArray!int(n); |
| scope(exit) myAllocator.dispose(a2); |
| ... |
| } |
| ---- |
| |
| In this case, `myAllocator` does not obey the `IAllocator` interface, but |
| implements its primitives so it can work with `makeArray` by means of duck |
| typing. |
| |
| One important thing to note about this setup is that statically-typed assembled |
| allocators are almost always faster than allocators that go through |
| `IAllocator`. An important rule of thumb is: "assemble allocator first, adapt |
| to `IAllocator` after". A good allocator implements intricate logic by means of |
| template assembly, and gets wrapped with `IAllocator` (usually by means of |
| $(LREF allocatorObject)) only once, at client level. |
| |
| Copyright: Andrei Alexandrescu 2013-. |
| |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| |
| Authors: $(HTTP erdani.com, Andrei Alexandrescu) |
| |
| Source: $(PHOBOSSRC std/experimental/allocator) |
| |
| */ |
| |
| module std.experimental.allocator; |
| |
| public import std.experimental.allocator.common, |
| std.experimental.allocator.typed; |
| |
| // Fix https://issues.dlang.org/show_bug.cgi?id=17806 |
| // this should always be the first unittest in this module in order to ensure |
| // that we use the `processAllocator` setter before the getter |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| auto newAlloc = sharedAllocatorObject(Mallocator.instance); |
| processAllocator = newAlloc; |
| assert(processAllocator is newAlloc); |
| processAllocator = sharedAllocatorObject(GCAllocator.instance); |
| } |
| |
| // Example in the synopsis above |
| @system unittest |
| { |
| import std.algorithm.comparison : min, max; |
| import std.experimental.allocator.building_blocks.allocator_list |
| : AllocatorList; |
| import std.experimental.allocator.building_blocks.bitmapped_block |
| : BitmappedBlock; |
| import std.experimental.allocator.building_blocks.bucketizer : Bucketizer; |
| import std.experimental.allocator.building_blocks.free_list : FreeList; |
| import std.experimental.allocator.building_blocks.segregator : Segregator; |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| |
| alias FList = FreeList!(GCAllocator, 0, unbounded); |
| alias A = Segregator!( |
| 8, FreeList!(GCAllocator, 0, 8), |
| 128, Bucketizer!(FList, 1, 128, 16), |
| 256, Bucketizer!(FList, 129, 256, 32), |
| 512, Bucketizer!(FList, 257, 512, 64), |
| 1024, Bucketizer!(FList, 513, 1024, 128), |
| 2048, Bucketizer!(FList, 1025, 2048, 256), |
| 3584, Bucketizer!(FList, 2049, 3584, 512), |
| 4072 * 1024, AllocatorList!( |
| (n) => BitmappedBlock!(4096)( |
| cast(ubyte[])(GCAllocator.instance.allocate( |
| max(n, 4072 * 1024))))), |
| GCAllocator |
| ); |
| A tuMalloc; |
| auto b = tuMalloc.allocate(500); |
| assert(b.length == 500); |
| auto c = tuMalloc.allocate(113); |
| assert(c.length == 113); |
| assert(tuMalloc.expand(c, 14)); |
| tuMalloc.deallocate(b); |
| tuMalloc.deallocate(c); |
| } |
| |
| import std.range.primitives; |
| import std.traits; |
| import std.typecons; |
| |
| /** |
| Dynamic allocator interface. Code that defines allocators ultimately implements |
| this interface. This should be used wherever a uniform type is required for |
| encapsulating various allocator implementations. |
| |
| Composition of allocators is not recommended at this level due to |
| inflexibility of dynamic interfaces and inefficiencies caused by cascaded |
| multiple calls. Instead, compose allocators using the static interface defined |
| in $(MREF std,experimental,allocator,building_blocks), |
| then adapt the composed |
| allocator to `IAllocator` (possibly by using $(LREF CAllocatorImpl) below). |
| |
| Methods returning `Ternary` return `Ternary.yes` upon success, |
| `Ternary.no` upon failure, and `Ternary.unknown` if the primitive is not |
| implemented by the allocator instance. |
| */ |
| interface IAllocator |
| { |
| nothrow: |
| /** |
| Returns the alignment offered. |
| */ |
| @property uint alignment(); |
| |
| /** |
| Returns the good allocation size that guarantees zero internal |
| fragmentation. |
| */ |
| size_t goodAllocSize(size_t s); |
| |
| /** |
| Allocates `n` bytes of memory. |
| */ |
| void[] allocate(size_t, TypeInfo ti = null); |
| |
| /** |
| Allocates `n` bytes of memory with specified alignment `a`. Implementations |
| that do not support this primitive should always return `null`. |
| */ |
| void[] alignedAllocate(size_t n, uint a); |
| |
| /** |
| Allocates and returns all memory available to this allocator. |
| Implementations that do not support this primitive should always return |
| `null`. |
| */ |
| void[] allocateAll(); |
| |
| /** |
| Expands a memory block in place and returns `true` if successful. |
| Implementations that don't support this primitive should always return |
| `false`. |
| */ |
| bool expand(ref void[], size_t); |
| |
| /// Reallocates a memory block. |
| bool reallocate(ref void[], size_t); |
| |
| /// Reallocates a memory block with specified alignment. |
| bool alignedReallocate(ref void[] b, size_t size, uint alignment); |
| |
| /** |
| Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if |
| the allocator doesn't own `b`, and `Ternary.unknown` if ownership |
| cannot be determined. Implementations that don't support this primitive |
| should always return `Ternary.unknown`. |
| */ |
| Ternary owns(void[] b); |
| |
| /** |
| Resolves an internal pointer to the full block allocated. Implementations |
| that don't support this primitive should always return `Ternary.unknown`. |
| */ |
| Ternary resolveInternalPointer(const void* p, ref void[] result); |
| |
| /** |
| Deallocates a memory block. Implementations that don't support this |
| primitive should always return `false`. A simple way to check that an |
| allocator supports deallocation is to call `deallocate(null)`. |
| */ |
| bool deallocate(void[] b); |
| |
| /** |
| Deallocates all memory. Implementations that don't support this primitive |
| should always return `false`. |
| */ |
| bool deallocateAll(); |
| |
| /** |
| Returns `Ternary.yes` if no memory is currently allocated from this |
| allocator, `Ternary.no` if some allocations are currently active, or |
| `Ternary.unknown` if not supported. |
| */ |
| Ternary empty(); |
| |
| /** |
| Increases the reference count of the concrete class that implements this |
| interface. |
| |
| For stateless allocators, this does nothing. |
| */ |
| @safe @nogc pure |
| void incRef(); |
| |
| /** |
| Decreases the reference count of the concrete class that implements this |
| interface. |
| When the reference count is `0`, the object self-destructs. |
| |
| Returns: `true` if the reference count is greater than `0` and `false` when |
| it hits `0`. For stateless allocators, it always returns `true`. |
| */ |
| @safe @nogc pure |
| bool decRef(); |
| } |
| |
| /** |
| A reference counted struct that wraps the dynamic allocator interface. |
| This should be used wherever a uniform type is required for encapsulating |
| various allocator implementations. |
| |
| Code that defines allocators ultimately implements the $(LREF IAllocator) |
| interface, possibly by using $(LREF CAllocatorImpl) below, and then build a |
| `RCIAllocator` out of this. |
| |
| Composition of allocators is not recommended at this level due to |
| inflexibility of dynamic interfaces and inefficiencies caused by cascaded |
| multiple calls. Instead, compose allocators using the static interface defined |
| in $(A std_experimental_allocator_building_blocks.html, |
| `std.experimental.allocator.building_blocks`), then adapt the composed |
| allocator to `RCIAllocator` (possibly by using $(LREF allocatorObject) below). |
| */ |
| struct RCIAllocator |
| { |
| private IAllocator _alloc; |
| |
| nothrow: |
| private @nogc pure @safe |
| this(this _)(IAllocator alloc) |
| { |
| assert(alloc); |
| _alloc = alloc; |
| } |
| |
| @nogc pure @safe |
| this(this) |
| { |
| if (_alloc !is null) |
| { |
| _alloc.incRef(); |
| } |
| } |
| |
| @nogc pure @safe |
| ~this() |
| { |
| if (_alloc !is null) |
| { |
| bool isLast = !_alloc.decRef(); |
| if (isLast) _alloc = null; |
| } |
| } |
| |
| @nogc pure @safe |
| auto ref opAssign()(typeof(this) rhs) |
| { |
| if (_alloc is rhs._alloc) |
| { |
| return this; |
| } |
| // incRef was allready called by rhs posblit, so we're just moving |
| // calling dtor is the equivalent of decRef |
| __dtor(); |
| _alloc = rhs._alloc; |
| // move |
| rhs._alloc = null; |
| return this; |
| } |
| |
| @nogc pure @safe |
| bool isNull(this _)() |
| { |
| return _alloc is null; |
| } |
| |
| @property uint alignment() |
| { |
| assert(_alloc); |
| return _alloc.alignment(); |
| } |
| |
| size_t goodAllocSize(size_t s) |
| { |
| assert(_alloc); |
| return _alloc.goodAllocSize(s); |
| } |
| |
| void[] allocate(size_t n, TypeInfo ti = null) |
| { |
| assert(_alloc); |
| return _alloc.allocate(n, ti); |
| } |
| |
| void[] alignedAllocate(size_t n, uint a) |
| { |
| assert(_alloc); |
| return _alloc.alignedAllocate(n, a); |
| } |
| |
| void[] allocateAll() |
| { |
| assert(_alloc); |
| return _alloc.allocateAll(); |
| } |
| |
| bool expand(ref void[] b, size_t size) |
| { |
| assert(_alloc); |
| return _alloc.expand(b, size); |
| } |
| |
| bool reallocate(ref void[] b, size_t size) |
| { |
| assert(_alloc); |
| return _alloc.reallocate(b, size); |
| } |
| |
| bool alignedReallocate(ref void[] b, size_t size, uint alignment) |
| { |
| assert(_alloc); |
| return _alloc.alignedReallocate(b, size, alignment); |
| } |
| |
| Ternary owns(void[] b) |
| { |
| assert(_alloc); |
| return _alloc.owns(b); |
| } |
| |
| Ternary resolveInternalPointer(const void* p, ref void[] result) |
| { |
| assert(_alloc); |
| return _alloc.resolveInternalPointer(p, result); |
| } |
| |
| bool deallocate(void[] b) |
| { |
| assert(_alloc); |
| return _alloc.deallocate(b); |
| } |
| |
| bool deallocateAll() |
| { |
| assert(_alloc); |
| return _alloc.deallocateAll(); |
| } |
| |
| Ternary empty() |
| { |
| assert(_alloc); |
| return _alloc.empty(); |
| } |
| } |
| |
| @system unittest |
| { |
| import std.experimental.allocator.building_blocks.region : Region; |
| import std.conv : emplace; |
| |
| auto reg = Region!()(new ubyte[1024]); |
| auto state = reg.allocate(stateSize!(CAllocatorImpl!(Region!(), Yes.indirect))); |
| auto regObj = emplace!(CAllocatorImpl!(Region!(), Yes.indirect))(state, ®); |
| |
| auto rcalloc = RCIAllocator(regObj); |
| auto b = rcalloc.allocate(10); |
| assert(b.length == 10); |
| |
| // The reference counting is zero based |
| assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); |
| { |
| auto rca2 = rcalloc; |
| assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 2); |
| } |
| assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); |
| } |
| |
| @system unittest |
| { |
| import std.conv; |
| import std.experimental.allocator.mallocator; |
| import std.experimental.allocator.building_blocks.stats_collector; |
| |
| alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); |
| SCAlloc statsCollectorAlloc; |
| |
| ulong bytesUsed = statsCollectorAlloc.bytesUsed; |
| assert(bytesUsed == 0); |
| |
| { |
| auto _allocator = allocatorObject(&statsCollectorAlloc); |
| bytesUsed = statsCollectorAlloc.bytesUsed; |
| assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc, Yes.indirect))); |
| } |
| |
| bytesUsed = statsCollectorAlloc.bytesUsed; |
| assert(bytesUsed == 0, "RCIAllocator leaks memory; leaked " |
| ~ to!string(bytesUsed) ~ " bytes"); |
| } |
| |
| @system unittest |
| { |
| import std.conv; |
| import std.experimental.allocator.mallocator; |
| import std.experimental.allocator.building_blocks.stats_collector; |
| |
| alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); |
| SCAlloc statsCollectorAlloc; |
| |
| ulong bytesUsed = statsCollectorAlloc.bytesUsed; |
| assert(bytesUsed == 0); |
| |
| { |
| auto _allocator = allocatorObject(statsCollectorAlloc); |
| |
| // Ensure that the allocator was passed through in CAllocatorImpl |
| // This allocator was used to allocate the chunk that holds the |
| // CAllocatorImpl object; which is it's own wrapper |
| bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; |
| assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), |
| "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); |
| _allocator.allocate(1); |
| bytesUsed = (cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed; |
| assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)) + 1, |
| "RCIAllocator leaks memory; leaked " ~ to!string(bytesUsed) ~ " bytes"); |
| } |
| |
| bytesUsed = statsCollectorAlloc.bytesUsed; |
| assert(bytesUsed == stateSize!(CAllocatorImpl!(SCAlloc)), |
| "RCIAllocator leaks memory; leaked " |
| ~ to!string(bytesUsed) ~ " bytes"); |
| } |
| |
| /** |
| Dynamic shared allocator interface. Code that defines allocators shareable |
| across threads ultimately implements this interface. This should be used |
| wherever a uniform type is required for encapsulating various allocator |
| implementations. |
| |
| Composition of allocators is not recommended at this level due to |
| inflexibility of dynamic interfaces and inefficiencies caused by cascaded |
| multiple calls. Instead, compose allocators using the static interface defined |
| in $(MREF std,experimental,allocator,building_blocks), |
| then adapt the composed |
| allocator to `ISharedAllocator` (possibly by using $(LREF CSharedAllocatorImpl) below). |
| |
| Methods returning `Ternary` return `Ternary.yes` upon success, |
| `Ternary.no` upon failure, and `Ternary.unknown` if the primitive is not |
| implemented by the allocator instance. |
| */ |
| interface ISharedAllocator |
| { |
| nothrow: |
| /** |
| Returns the alignment offered. |
| */ |
| @property uint alignment() shared; |
| |
| /** |
| Returns the good allocation size that guarantees zero internal |
| fragmentation. |
| */ |
| size_t goodAllocSize(size_t s) shared; |
| |
| /** |
| Allocates `n` bytes of memory. |
| */ |
| void[] allocate(size_t, TypeInfo ti = null) shared; |
| |
| /** |
| Allocates `n` bytes of memory with specified alignment `a`. Implementations |
| that do not support this primitive should always return `null`. |
| */ |
| void[] alignedAllocate(size_t n, uint a) shared; |
| |
| /** |
| Allocates and returns all memory available to this allocator. |
| Implementations that do not support this primitive should always return |
| `null`. |
| */ |
| void[] allocateAll() shared; |
| |
| /** |
| Expands a memory block in place and returns `true` if successful. |
| Implementations that don't support this primitive should always return |
| `false`. |
| */ |
| bool expand(ref void[], size_t) shared; |
| |
| /// Reallocates a memory block. |
| bool reallocate(ref void[], size_t) shared; |
| |
| /// Reallocates a memory block with specified alignment. |
| bool alignedReallocate(ref void[] b, size_t size, uint alignment) shared; |
| |
| /** |
| Returns `Ternary.yes` if the allocator owns `b`, `Ternary.no` if |
| the allocator doesn't own `b`, and `Ternary.unknown` if ownership |
| cannot be determined. Implementations that don't support this primitive |
| should always return `Ternary.unknown`. |
| */ |
| Ternary owns(void[] b) shared; |
| |
| /** |
| Resolves an internal pointer to the full block allocated. Implementations |
| that don't support this primitive should always return `Ternary.unknown`. |
| */ |
| Ternary resolveInternalPointer(const void* p, ref void[] result) shared; |
| |
| /** |
| Deallocates a memory block. Implementations that don't support this |
| primitive should always return `false`. A simple way to check that an |
| allocator supports deallocation is to call `deallocate(null)`. |
| */ |
| bool deallocate(void[] b) shared; |
| |
| /** |
| Deallocates all memory. Implementations that don't support this primitive |
| should always return `false`. |
| */ |
| bool deallocateAll() shared; |
| |
| /** |
| Returns `Ternary.yes` if no memory is currently allocated from this |
| allocator, `Ternary.no` if some allocations are currently active, or |
| `Ternary.unknown` if not supported. |
| */ |
| Ternary empty() shared; |
| |
| /** |
| Increases the reference count of the concrete class that implements this |
| interface. |
| |
| For stateless allocators, this does nothing. |
| */ |
| @safe @nogc pure |
| void incRef() shared; |
| |
| /** |
| Decreases the reference count of the concrete class that implements this |
| interface. |
| When the reference count is `0`, the object self-destructs. |
| |
| For stateless allocators, this does nothing. |
| |
| Returns: `true` if the reference count is greater than `0` and `false` when |
| it hits `0`. For stateless allocators, it always returns `true`. |
| */ |
| @safe @nogc pure |
| bool decRef() shared; |
| } |
| |
| /** |
| A reference counted struct that wraps the dynamic shared allocator interface. |
| This should be used wherever a uniform type is required for encapsulating |
| various allocator implementations. |
| |
| Code that defines allocators shareable across threads ultimately implements the |
| $(LREF ISharedAllocator) interface, possibly by using |
| $(LREF CSharedAllocatorImpl) below, and then build a `RCISharedAllocator` out |
| of this. |
| |
| Composition of allocators is not recommended at this level due to |
| inflexibility of dynamic interfaces and inefficiencies caused by cascaded |
| multiple calls. Instead, compose allocators using the static interface defined |
| in $(A std_experimental_allocator_building_blocks.html, |
| `std.experimental.allocator.building_blocks`), then adapt the composed allocator |
| to `RCISharedAllocator` (possibly by using $(LREF sharedAllocatorObject) below). |
| */ |
| shared struct RCISharedAllocator |
| { |
| private ISharedAllocator _alloc; |
| |
| nothrow: |
| private @nogc pure @safe |
| this(shared ISharedAllocator alloc) |
| { |
| assert(alloc); |
| _alloc = alloc; |
| } |
| |
| @nogc pure @safe |
| this(this) |
| { |
| if (_alloc !is null) |
| { |
| _alloc.incRef(); |
| } |
| } |
| |
| @nogc pure @safe |
| ~this() |
| { |
| if (_alloc !is null) |
| { |
| bool isLast = !_alloc.decRef(); |
| if (isLast) _alloc = null; |
| } |
| } |
| |
| @nogc pure @safe |
| auto ref opAssign()(RCISharedAllocator rhs) |
| { |
| if (_alloc is rhs._alloc) |
| { |
| return this; |
| } |
| // incRef was allready called by rhs posblit, so we're just moving |
| if (_alloc !is null) |
| { |
| _alloc.decRef(); |
| } |
| _alloc = rhs._alloc; |
| // move |
| rhs._alloc = null; |
| return this; |
| } |
| |
| @nogc pure @safe |
| bool isNull(this _)() |
| { |
| return _alloc is null; |
| } |
| |
| @property uint alignment() |
| { |
| assert(_alloc); |
| return _alloc.alignment(); |
| } |
| |
| size_t goodAllocSize(size_t s) |
| { |
| assert(_alloc); |
| return _alloc.goodAllocSize(s); |
| } |
| |
| void[] allocate(size_t n, TypeInfo ti = null) |
| { |
| assert(_alloc); |
| return _alloc.allocate(n, ti); |
| } |
| |
| void[] alignedAllocate(size_t n, uint a) |
| { |
| assert(_alloc); |
| return _alloc.alignedAllocate(n, a); |
| } |
| |
| void[] allocateAll() |
| { |
| assert(_alloc); |
| return _alloc.allocateAll(); |
| } |
| |
| bool expand(ref void[] b, size_t size) |
| { |
| assert(_alloc); |
| return _alloc.expand(b, size); |
| } |
| |
| bool reallocate(ref void[] b, size_t size) |
| { |
| assert(_alloc); |
| return _alloc.reallocate(b, size); |
| } |
| |
| bool alignedReallocate(ref void[] b, size_t size, uint alignment) |
| { |
| assert(_alloc); |
| return _alloc.alignedReallocate(b, size, alignment); |
| } |
| |
| Ternary owns(void[] b) |
| { |
| assert(_alloc); |
| return _alloc.owns(b); |
| } |
| |
| Ternary resolveInternalPointer(const void* p, ref void[] result) |
| { |
| assert(_alloc); |
| return _alloc.resolveInternalPointer(p, result); |
| } |
| |
| bool deallocate(void[] b) |
| { |
| assert(_alloc); |
| return _alloc.deallocate(b); |
| } |
| |
| bool deallocateAll() |
| { |
| assert(_alloc); |
| return _alloc.deallocateAll(); |
| } |
| |
| Ternary empty() |
| { |
| assert(_alloc); |
| return _alloc.empty(); |
| } |
| } |
| |
| private RCISharedAllocator _processAllocator; |
| private RCIAllocator _threadAllocator; |
| |
| @nogc nothrow @safe |
| private ref RCIAllocator setupThreadAllocator() |
| { |
| /* |
| Forwards the `_threadAllocator` calls to the `processAllocator` |
| */ |
| static class ThreadAllocator : IAllocator |
| { |
| nothrow: |
| private RCISharedAllocator _allocator; |
| |
| @nogc @safe |
| this(ref RCISharedAllocator procAlloc) |
| { |
| _allocator = procAlloc; |
| } |
| |
| override @property uint alignment() |
| { |
| return _allocator.alignment(); |
| } |
| |
| override size_t goodAllocSize(size_t s) |
| { |
| return _allocator.goodAllocSize(s); |
| } |
| |
| override void[] allocate(size_t n, TypeInfo ti = null) |
| { |
| return _allocator.allocate(n, ti); |
| } |
| |
| override void[] alignedAllocate(size_t n, uint a) |
| { |
| return _allocator.alignedAllocate(n, a); |
| } |
| |
| override void[] allocateAll() |
| { |
| return _allocator.allocateAll(); |
| } |
| |
| override bool expand(ref void[] b, size_t size) |
| { |
| return _allocator.expand(b, size); |
| } |
| |
| override bool reallocate(ref void[] b, size_t size) |
| { |
| return _allocator.reallocate(b, size); |
| } |
| |
| override bool alignedReallocate(ref void[] b, size_t size, uint alignment) |
| { |
| return _allocator.alignedReallocate(b, size, alignment); |
| } |
| |
| override Ternary owns(void[] b) |
| { |
| return _allocator.owns(b); |
| } |
| |
| override Ternary resolveInternalPointer(const void* p, ref void[] result) |
| { |
| return _allocator.resolveInternalPointer(p, result); |
| } |
| |
| override bool deallocate(void[] b) |
| { |
| return _allocator.deallocate(b); |
| } |
| |
| override bool deallocateAll() |
| { |
| return _allocator.deallocateAll(); |
| } |
| |
| override Ternary empty() |
| { |
| return _allocator.empty(); |
| } |
| |
| @nogc pure @safe |
| override void incRef() |
| { |
| _allocator._alloc.incRef(); |
| } |
| |
| @nogc pure @safe |
| override bool decRef() |
| { |
| return _allocator._alloc.decRef(); |
| } |
| } |
| |
| assert(_threadAllocator.isNull); |
| import core.lifetime : emplace; |
| static ulong[stateSize!(ThreadAllocator).divideRoundUp(ulong.sizeof)] _threadAllocatorState; |
| () @trusted { |
| _threadAllocator = RCIAllocator(emplace!(ThreadAllocator)(_threadAllocatorState[], processAllocator())); |
| }(); |
| return _threadAllocator; |
| } |
| |
| // Fix threadAllocator bug: the threadAllocator should hold an internal reference |
| // to the processAllocator that it's using |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| auto a = sharedAllocatorObject(Mallocator.instance); |
| auto buf = theAllocator.allocate(42); |
| processAllocator = a; |
| theAllocator.deallocate(buf); |
| } |
| |
| /** |
| Gets/sets the allocator for the current thread. This is the default allocator |
| that should be used for allocating thread-local memory. For allocating memory |
| to be shared across threads, use `processAllocator` (below). By default, |
| `theAllocator` ultimately fetches memory from `processAllocator`, which |
| in turn uses the garbage collected heap. |
| */ |
| @nogc nothrow @safe |
| @property ref RCIAllocator theAllocator() |
| { |
| alias p = _threadAllocator; |
| return !p.isNull() ? p : setupThreadAllocator(); |
| } |
| |
| /// Ditto |
| nothrow @system @nogc |
| @property void theAllocator(RCIAllocator a) |
| { |
| assert(!a.isNull); |
| _threadAllocator = a; |
| } |
| |
| /// |
| @system unittest |
| { |
| // Install a new allocator that is faster for 128-byte allocations. |
| import std.experimental.allocator.building_blocks.free_list : FreeList; |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| auto oldAllocator = theAllocator; |
| scope(exit) theAllocator = oldAllocator; |
| theAllocator = allocatorObject(FreeList!(GCAllocator, 128)()); |
| // Use the now changed allocator to allocate an array |
| const ubyte[] arr = theAllocator.makeArray!ubyte(128); |
| assert(arr.ptr); |
| //... |
| } |
| |
| /** |
| Gets/sets the allocator for the current process. This allocator must be used |
| for allocating memory shared across threads. Objects created using this |
| allocator can be cast to `shared`. |
| */ |
| @nogc nothrow @trusted |
| @property ref RCISharedAllocator processAllocator() |
| { |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| import std.concurrency : initOnce; |
| |
| static RCISharedAllocator* forceAttributes() |
| { |
| return &initOnce!_processAllocator( |
| sharedAllocatorObject(GCAllocator.instance)); |
| } |
| |
| return *(cast(RCISharedAllocator* function() @nogc nothrow)(&forceAttributes))(); |
| } |
| |
| /// Ditto |
| @nogc nothrow @system |
| @property void processAllocator(ref RCISharedAllocator a) |
| { |
| assert(!a.isNull); |
| processAllocator() = a; |
| } |
| |
| @system unittest |
| { |
| import core.exception : AssertError; |
| import std.exception : assertThrown; |
| import std.experimental.allocator.building_blocks.free_list : SharedFreeList; |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| assert(!processAllocator.isNull); |
| assert(!theAllocator.isNull); |
| |
| testAllocatorObject(processAllocator); |
| testAllocatorObject(theAllocator); |
| |
| shared SharedFreeList!(Mallocator, chooseAtRuntime, chooseAtRuntime) sharedFL; |
| RCISharedAllocator sharedFLObj = sharedAllocatorObject(sharedFL); |
| alias SharedAllocT = CSharedAllocatorImpl!( |
| shared SharedFreeList!( |
| Mallocator, chooseAtRuntime, chooseAtRuntime)); |
| |
| assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); |
| assert(!sharedFLObj.isNull); |
| testAllocatorObject(sharedFLObj); |
| |
| // Test processAllocator setter |
| RCISharedAllocator oldProcessAllocator = processAllocator; |
| processAllocator = sharedFLObj; |
| assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 2); |
| assert(processAllocator._alloc is sharedFLObj._alloc); |
| |
| testAllocatorObject(processAllocator); |
| testAllocatorObject(theAllocator); |
| assertThrown!AssertError(processAllocator = RCISharedAllocator(null)); |
| |
| // Restore initial processAllocator state |
| processAllocator = oldProcessAllocator; |
| assert((cast(SharedAllocT)(sharedFLObj._alloc)).rc == 1); |
| assert(processAllocator is oldProcessAllocator); |
| |
| RCISharedAllocator indirectShFLObj = sharedAllocatorObject(&sharedFL); |
| testAllocatorObject(indirectShFLObj); |
| alias IndirectSharedAllocT = CSharedAllocatorImpl!( |
| shared SharedFreeList!( |
| Mallocator, chooseAtRuntime, chooseAtRuntime) |
| , Yes.indirect); |
| |
| assert((cast(IndirectSharedAllocT)(indirectShFLObj._alloc)).rc == 1); |
| |
| RCIAllocator indirectMallocator = allocatorObject(&Mallocator.instance); |
| testAllocatorObject(indirectMallocator); |
| } |
| |
| /** |
| Dynamically allocates (using `alloc`) and then creates in the memory |
| allocated an object of type `T`, using `args` (if any) for its |
| initialization. Initialization occurs in the memory allocated and is otherwise |
| semantically the same as `T(args)`. |
| (Note that using `alloc.make!(T[])` creates a pointer to an (empty) array |
| of `T`s, not an array. To use an allocator to allocate and initialize an |
| array, use `alloc.makeArray!T` described below.) |
| |
| Params: |
| T = Type of the object being created. |
| alloc = The allocator used for getting the needed memory. It may be an object |
| implementing the static interface for allocators, or an `IAllocator` |
| reference. |
| args = Optional arguments used for initializing the created object. If not |
| present, the object is default constructed. |
| |
| Returns: If `T` is a class type, returns a reference to the created `T` |
| object. Otherwise, returns a `T*` pointing to the created object. In all |
| cases, returns `null` if allocation failed. |
| |
| Throws: If `T`'s constructor throws, deallocates the allocated memory and |
| propagates the exception. |
| */ |
| auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args) |
| { |
| import std.algorithm.comparison : max; |
| static if (!is(T == class) && !is(T == interface) && A.length == 0 |
| && __traits(compiles, {T t;}) && __traits(isZeroInit, T) |
| && is(typeof(alloc.allocateZeroed(size_t.max)))) |
| { |
| auto m = alloc.allocateZeroed(max(T.sizeof, 1)); |
| return (() @trusted => cast(T*) m.ptr)(); |
| } |
| else |
| { |
| import core.internal.lifetime : emplaceRef; |
| import core.lifetime : emplace; |
| |
| auto m = alloc.allocate(max(stateSize!T, 1)); |
| if (!m.ptr) return null; |
| |
| // make can only be @safe if emplace or emplaceRef is `pure` |
| auto construct() |
| { |
| static if (is(T == class)) return emplace!T(m, args); |
| else |
| { |
| // Assume cast is safe as allocation succeeded for `stateSize!T` |
| auto p = () @trusted { return cast(T*) m.ptr; }(); |
| emplaceRef!T(*p, args); |
| return p; |
| } |
| } |
| |
| scope(failure) |
| { |
| static if (is(typeof(() pure { return construct(); }))) |
| { |
| // Assume deallocation is safe because: |
| // 1) in case of failure, `m` is the only reference to this memory |
| // 2) `m` is known to originate from `alloc` |
| () @trusted { alloc.deallocate(m); }(); |
| } |
| else |
| { |
| alloc.deallocate(m); |
| } |
| } |
| |
| return construct(); |
| } |
| } |
| |
| /// |
| @system unittest |
| { |
| // Dynamically allocate one integer |
| const int* p1 = theAllocator.make!int; |
| // It's implicitly initialized with its .init value |
| assert(*p1 == 0); |
| // Dynamically allocate one double, initialize to 42.5 |
| const double* p2 = theAllocator.make!double(42.5); |
| assert(*p2 == 42.5); |
| |
| // Dynamically allocate a struct |
| static struct Point |
| { |
| int x, y, z; |
| } |
| // Use the generated constructor taking field values in order |
| const Point* p = theAllocator.make!Point(1, 2); |
| assert(p.x == 1 && p.y == 2 && p.z == 0); |
| |
| // Dynamically allocate a class object |
| static class Customer |
| { |
| uint id = uint.max; |
| this() {} |
| this(uint id) { this.id = id; } |
| // ... |
| } |
| Customer cust = theAllocator.make!Customer; |
| assert(cust.id == uint.max); // default initialized |
| cust = theAllocator.make!Customer(42); |
| assert(cust.id == 42); |
| |
| // explicit passing of outer pointer |
| static class Outer |
| { |
| int x = 3; |
| class Inner |
| { |
| auto getX() { return x; } |
| } |
| } |
| auto outer = theAllocator.make!Outer(); |
| auto inner = theAllocator.make!(Outer.Inner)(outer); |
| assert(outer.x == inner.getX); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=15639 |
| // https://issues.dlang.org/show_bug.cgi?id=15772 |
| @system unittest |
| { |
| abstract class Foo {} |
| class Bar: Foo {} |
| static assert(!is(typeof(theAllocator.make!Foo))); |
| static assert( is(typeof(theAllocator.make!Bar))); |
| } |
| |
| @system unittest |
| { |
| void test(Allocator)(auto ref Allocator alloc) |
| { |
| const int* a = alloc.make!int(10); |
| assert(*a == 10); |
| |
| struct A |
| { |
| int x; |
| string y; |
| double z; |
| } |
| |
| A* b = alloc.make!A(42); |
| assert(b.x == 42); |
| assert(b.y is null); |
| import std.math.traits : isNaN; |
| assert(b.z.isNaN); |
| |
| b = alloc.make!A(43, "44", 45); |
| assert(b.x == 43); |
| assert(b.y == "44"); |
| assert(b.z == 45); |
| |
| static class B |
| { |
| int x; |
| string y; |
| double z; |
| this(int _x, string _y = null, double _z = double.init) |
| { |
| x = _x; |
| y = _y; |
| z = _z; |
| } |
| } |
| |
| B c = alloc.make!B(42); |
| assert(c.x == 42); |
| assert(c.y is null); |
| assert(c.z.isNaN); |
| |
| c = alloc.make!B(43, "44", 45); |
| assert(c.x == 43); |
| assert(c.y == "44"); |
| assert(c.z == 45); |
| |
| const parray = alloc.make!(int[]); |
| assert((*parray).empty); |
| } |
| |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| test(GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| // Attribute propagation |
| nothrow @safe @nogc unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| alias alloc = Mallocator.instance; |
| |
| void test(T, Args...)(auto ref Args args) |
| { |
| auto k = alloc.make!T(args); |
| () @trusted { alloc.dispose(k); }(); |
| } |
| |
| test!int; |
| test!(int*); |
| test!int(0); |
| test!(int*)(null); |
| } |
| |
| // should be pure with the GCAllocator |
| /*pure nothrow*/ @safe unittest |
| { |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| |
| alias alloc = GCAllocator.instance; |
| |
| void test(T, Args...)(auto ref Args args) |
| { |
| auto k = alloc.make!T(args); |
| (a) @trusted { a.dispose(k); }(alloc); |
| } |
| |
| test!int(); |
| test!(int*); |
| test!int(0); |
| test!(int*)(null); |
| } |
| |
| // Verify that making an object by calling an impure constructor is not @safe |
| nothrow @safe @nogc unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| static struct Pure { this(int) pure nothrow @nogc @safe {} } |
| |
| cast(void) Mallocator.instance.make!Pure(0); |
| |
| static int g = 0; |
| static struct Impure { this(int) nothrow @nogc @safe { |
| g++; |
| } } |
| static assert(!__traits(compiles, cast(void) Mallocator.instance.make!Impure(0))); |
| } |
| |
| // test failure with a pure, failing struct |
| @safe unittest |
| { |
| import std.exception : assertThrown, enforce; |
| |
| // this struct can't be initialized |
| struct InvalidStruct |
| { |
| this(int b) |
| { |
| enforce(1 == 2); |
| } |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| assertThrown(make!InvalidStruct(Mallocator.instance, 42)); |
| } |
| |
| // test failure with an impure, failing struct |
| @system unittest |
| { |
| import std.exception : assertThrown, enforce; |
| static int g; |
| struct InvalidImpureStruct |
| { |
| this(int b) |
| { |
| g++; |
| enforce(1 == 2); |
| } |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| assertThrown(make!InvalidImpureStruct(Mallocator.instance, 42)); |
| } |
| |
| // Don't allow zero-ctor-args `make` for structs with `@disable this();` |
| @system unittest |
| { |
| struct NoDefaultCtor |
| { |
| int i; |
| @disable this(); |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| static assert(!__traits(compiles, make!NoDefaultCtor(Mallocator.instance)), |
| "Don't allow zero-ctor-args `make` for structs with `@disable this();`"); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=18937 |
| @safe unittest |
| { |
| static struct S |
| { |
| ubyte[16 * 1024] data; |
| } |
| |
| static struct SomeAllocator |
| { |
| ubyte[] allocate(size_t) { return []; } |
| void deallocate(void[]) {} |
| } |
| |
| auto x = SomeAllocator().make!S(); |
| } |
| |
| private void fillWithMemcpy(T)(scope void[] array, auto ref T filler) nothrow |
| if (T.sizeof == 1) |
| { |
| import core.stdc.string : memset; |
| import std.traits : CopyConstness; |
| if (!array.length) return; |
| memset(array.ptr, *cast(CopyConstness!(T*, ubyte*)) &filler, array.length); |
| } |
| |
| private void fillWithMemcpy(T)(scope void[] array, auto ref T filler) nothrow |
| if (T.sizeof != 1) |
| { |
| import core.stdc.string : memcpy; |
| import std.algorithm.comparison : min; |
| if (!array.length) return; |
| memcpy(array.ptr, &filler, T.sizeof); |
| // Fill the array from the initialized portion of itself exponentially. |
| for (size_t offset = T.sizeof; offset < array.length; ) |
| { |
| size_t extent = min(offset, array.length - offset); |
| memcpy(array.ptr + offset, array.ptr, extent); |
| offset += extent; |
| } |
| } |
| |
| @system unittest |
| { |
| // Test T.sizeof == 1 path of fillWithMemcpy. |
| ubyte[] a; |
| fillWithMemcpy(a, ubyte(42)); |
| assert(a.length == 0); |
| a = [ 1, 2, 3, 4, 5 ]; |
| fillWithMemcpy(a, ubyte(42)); |
| assert(a == [ 42, 42, 42, 42, 42]); |
| } |
| |
| @system unittest |
| { |
| int[] a; |
| fillWithMemcpy(a, 42); |
| assert(a.length == 0); |
| a = [ 1, 2, 3, 4, 5 ]; |
| fillWithMemcpy(a, 42); |
| assert(a == [ 42, 42, 42, 42, 42]); |
| } |
| |
| //Make shared object |
| @system unittest |
| { |
| import core.atomic : atomicLoad; |
| auto psi = theAllocator.make!(shared(int))(10); |
| assert(10 == (*psi).atomicLoad()); |
| } |
| |
| private T[] uninitializedFillDefault(T)(T[] array) nothrow |
| { |
| static if (__traits(isZeroInit, T)) |
| { |
| import core.stdc.string : memset; |
| if (array !is null) |
| memset(array.ptr, 0, T.sizeof * array.length); |
| return array; |
| } |
| else static if (is(immutable T == immutable char) || is(immutable T == immutable wchar)) |
| { |
| import core.stdc.string : memset; |
| if (array !is null) |
| memset(array.ptr, 0xff, T.sizeof * array.length); |
| return array; |
| } |
| else |
| { |
| T t = T.init; |
| fillWithMemcpy(array, t); |
| return array; |
| } |
| } |
| |
| pure nothrow @nogc |
| @system unittest |
| { |
| static struct S { int x = 42; @disable this(this); } |
| |
| int[5] expected = [42, 42, 42, 42, 42]; |
| S[5] arr = void; |
| uninitializedFillDefault(arr); |
| assert((cast(int*) arr.ptr)[0 .. arr.length] == expected); |
| } |
| |
| @system unittest |
| { |
| int[] a = [1, 2, 4]; |
| uninitializedFillDefault(a); |
| assert(a == [0, 0, 0]); |
| |
| char[] b = [1, 2, 4]; |
| uninitializedFillDefault(b); |
| assert(b == [0xff, 0xff, 0xff]); |
| |
| wchar[] c = [1, 2, 4]; |
| uninitializedFillDefault(c); |
| assert(c == [0xffff, 0xffff, 0xffff]); |
| } |
| |
| @system unittest |
| { |
| static struct P { float x = 0; float y = 0; } |
| |
| static assert(__traits(isZeroInit, P)); |
| P[] a = [P(10, 11), P(20, 21), P(40, 41)]; |
| uninitializedFillDefault(a); |
| assert(a == [P.init, P.init, P.init]); |
| } |
| |
| /** |
| Create an array of `T` with `length` elements using `alloc`. The array is either default-initialized, filled with copies of `init`, or initialized with values fetched from `range`. |
| |
| Params: |
| T = element type of the array being created |
| alloc = the allocator used for getting memory |
| length = length of the newly created array |
| init = element used for filling the array |
| range = range used for initializing the array elements |
| |
| Returns: |
| The newly-created array, or `null` if either `length` was `0` or |
| allocation failed. |
| |
| Throws: |
| The first two overloads throw only if `alloc`'s primitives do. The |
| overloads that involve copy initialization deallocate memory and propagate the |
| exception if the copy operation throws. |
| */ |
| T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length) |
| { |
| if (!length) return null; |
| static if (T.sizeof <= 1) |
| { |
| const nAlloc = length * T.sizeof; |
| } |
| else |
| { |
| import core.checkedint : mulu; |
| bool overflow; |
| const nAlloc = mulu(length, T.sizeof, overflow); |
| if (overflow) return null; |
| } |
| |
| static if (__traits(isZeroInit, T) && hasMember!(Allocator, "allocateZeroed")) |
| { |
| auto m = alloc.allocateZeroed(nAlloc); |
| return (() @trusted => cast(T[]) m)(); |
| } |
| else |
| { |
| auto m = alloc.allocate(nAlloc); |
| if (!m.ptr) return null; |
| alias U = Unqual!T; |
| return () @trusted { return cast(T[]) uninitializedFillDefault(cast(U[]) m); }(); |
| } |
| } |
| |
| @system unittest |
| { |
| void test1(A)(auto ref A alloc) |
| { |
| int[] a = alloc.makeArray!int(0); |
| assert(a.length == 0 && a.ptr is null); |
| a = alloc.makeArray!int(5); |
| assert(a.length == 5); |
| static immutable cheatsheet = [0, 0, 0, 0, 0]; |
| assert(a == cheatsheet); |
| } |
| |
| void test2(A)(auto ref A alloc) |
| { |
| static struct S { int x = 42; @disable this(this); } |
| S[] arr = alloc.makeArray!S(5); |
| assert(arr.length == 5); |
| int[] arrInt = () @trusted { return (cast(int*) arr.ptr)[0 .. 5]; }(); |
| static immutable res = [42, 42, 42, 42, 42]; |
| assert(arrInt == res); |
| } |
| |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| import std.experimental.allocator.mallocator : Mallocator; |
| (alloc) /*pure nothrow*/ @safe { test1(alloc); test2(alloc);} (GCAllocator.instance); |
| (alloc) nothrow @safe @nogc { test1(alloc); test2(alloc);} (Mallocator.instance); |
| test2(theAllocator); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = theAllocator.makeArray!(shared int)(5); |
| static assert(is(typeof(a) == shared(int)[])); |
| assert(a.length == 5); |
| assert(a.equal([0, 0, 0, 0, 0])); |
| |
| auto b = theAllocator.makeArray!(const int)(5); |
| static assert(is(typeof(b) == const(int)[])); |
| assert(b.length == 5); |
| assert(b.equal([0, 0, 0, 0, 0])); |
| |
| auto c = theAllocator.makeArray!(immutable int)(5); |
| static assert(is(typeof(c) == immutable(int)[])); |
| assert(c.length == 5); |
| assert(c.equal([0, 0, 0, 0, 0])); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=19085 - makeArray with void |
| @system unittest |
| { |
| auto b = theAllocator.makeArray!void(5); |
| scope(exit) theAllocator.dispose(b); |
| auto c = cast(ubyte[]) b; |
| assert(c.length == 5); |
| assert(c == [0, 0, 0, 0, 0]); // default initialization |
| } |
| |
| private enum hasPurePostblit(T) = !hasElaborateCopyConstructor!T || |
| is(typeof(() pure { T.init.__xpostblit(); })); |
| |
| private enum hasPureDtor(T) = !hasElaborateDestructor!T || |
| is(typeof(() pure { T.init.__xdtor(); })); |
| |
| // `true` when postblit and destructor of T cannot escape references to itself |
| private enum canSafelyDeallocPostRewind(T) = hasPurePostblit!T && hasPureDtor!T; |
| |
| /// Ditto |
| T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, T init) |
| { |
| if (!length) return null; |
| auto m = alloc.allocate(T.sizeof * length); |
| if (!m.ptr) return null; |
| auto result = () @trusted { return cast(T[]) m; } (); |
| import std.traits : hasElaborateCopyConstructor; |
| static if (hasElaborateCopyConstructor!T) |
| { |
| scope(failure) |
| { |
| static if (canSafelyDeallocPostRewind!T) |
| () @trusted { alloc.deallocate(m); } (); |
| else |
| alloc.deallocate(m); |
| } |
| |
| size_t i = 0; |
| static if (hasElaborateDestructor!T) |
| { |
| scope (failure) |
| { |
| foreach (j; 0 .. i) |
| { |
| destroy(result[j]); |
| } |
| } |
| } |
| import core.lifetime : emplace; |
| for (; i < length; ++i) |
| { |
| emplace!T(&result[i], init); |
| } |
| } |
| else |
| { |
| alias U = Unqual!T; |
| () @trusted { fillWithMemcpy(cast(U[]) result, *(cast(U*) &init)); }(); |
| } |
| return result; |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| static void test(T)() |
| { |
| T[] a = theAllocator.makeArray!T(2); |
| assert(a.equal([0, 0])); |
| a = theAllocator.makeArray!T(3, 42); |
| assert(a.equal([42, 42, 42])); |
| import std.range : only; |
| a = theAllocator.makeArray!T(only(42, 43, 44)); |
| assert(a.equal([42, 43, 44])); |
| } |
| test!int(); |
| test!(shared int)(); |
| test!(const int)(); |
| test!(immutable int)(); |
| } |
| |
| @system unittest |
| { |
| void test(T)(in T initialValue) |
| { |
| auto t = theAllocator.makeArray!T(100, initialValue); |
| //auto t = theAllocator.makeArray(100, initialValue); // works well with the old code |
| } |
| |
| const int init = 3; |
| test(init); |
| } |
| |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| long[] a = alloc.makeArray!long(0, 42); |
| assert(a.length == 0 && a.ptr is null); |
| a = alloc.makeArray!long(5, 42); |
| assert(a.length == 5); |
| assert(a == [ 42, 42, 42, 42, 42 ]); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| (alloc) /*pure nothrow*/ @safe { test(alloc); } (GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| // test failure with a pure, failing struct |
| @safe unittest |
| { |
| import std.exception : assertThrown, enforce; |
| |
| struct NoCopy |
| { |
| @disable this(); |
| |
| this(int b){} |
| |
| // can't be copied |
| this(this) |
| { |
| enforce(1 == 2); |
| } |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| assertThrown(makeArray!NoCopy(Mallocator.instance, 10, NoCopy(42))); |
| } |
| |
| // test failure with an impure, failing struct |
| @system unittest |
| { |
| import std.exception : assertThrown, enforce; |
| |
| static int i = 0; |
| struct Singleton |
| { |
| @disable this(); |
| |
| this(int b){} |
| |
| // can't be copied |
| this(this) |
| { |
| enforce(i++ == 0); |
| } |
| |
| ~this() |
| { |
| i--; |
| } |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| assertThrown(makeArray!Singleton(Mallocator.instance, 10, Singleton(42))); |
| } |
| |
| /// Ditto |
| Unqual!(ElementEncodingType!R)[] makeArray(Allocator, R)(auto ref Allocator alloc, R range) |
| if (isInputRange!R && !isInfinite!R) |
| { |
| alias T = Unqual!(ElementEncodingType!R); |
| return makeArray!(T, Allocator, R)(alloc, range); |
| } |
| |
| /// Ditto |
| T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range) |
| if (isInputRange!R && !isInfinite!R) |
| { |
| static if (isForwardRange!R || hasLength!R) |
| { |
| static if (hasLength!R || isNarrowString!R) |
| immutable length = range.length; |
| else |
| immutable length = range.save.walkLength; |
| |
| if (!length) return null; |
| auto m = alloc.allocate(T.sizeof * length); |
| if (!m.ptr) return null; |
| auto result = () @trusted { return cast(T[]) m; } (); |
| |
| size_t i = 0; |
| scope (failure) |
| { |
| foreach (j; 0 .. i) |
| { |
| auto p = () @trusted { return cast(Unqual!T*) &result[j]; }(); |
| destroy(p); |
| } |
| |
| static if (canSafelyDeallocPostRewind!T) |
| () @trusted { alloc.deallocate(m); } (); |
| else |
| alloc.deallocate(m); |
| } |
| |
| import core.internal.lifetime : emplaceRef; |
| static if (isNarrowString!R || isRandomAccessRange!R) |
| { |
| foreach (j; 0 .. range.length) |
| { |
| emplaceRef!T(result[i++], range[j]); |
| } |
| } |
| else |
| { |
| for (; !range.empty; range.popFront, ++i) |
| { |
| emplaceRef!T(result[i], range.front); |
| } |
| } |
| |
| return result; |
| } |
| else |
| { |
| // Estimated size |
| size_t estimated = 8; |
| auto m = alloc.allocate(T.sizeof * estimated); |
| if (!m.ptr) return null; |
| auto result = () @trusted { return cast(T[]) m; } (); |
| |
| size_t initialized = 0; |
| void bailout() |
| { |
| foreach (i; 0 .. initialized + 1) |
| { |
| destroy(result[i]); |
| } |
| |
| static if (canSafelyDeallocPostRewind!T) |
| () @trusted { alloc.deallocate(m); } (); |
| else |
| alloc.deallocate(m); |
| } |
| scope (failure) bailout; |
| |
| for (; !range.empty; range.popFront, ++initialized) |
| { |
| if (initialized == estimated) |
| { |
| // Need to reallocate |
| static if (hasPurePostblit!T) |
| auto success = () @trusted { return alloc.reallocate(m, T.sizeof * (estimated *= 2)); } (); |
| else |
| auto success = alloc.reallocate(m, T.sizeof * (estimated *= 2)); |
| if (!success) |
| { |
| bailout; |
| return null; |
| } |
| result = () @trusted { return cast(T[]) m; } (); |
| } |
| import core.internal.lifetime : emplaceRef; |
| emplaceRef(result[initialized], range.front); |
| } |
| |
| if (initialized < estimated) |
| { |
| // Try to shrink memory, no harm if not possible |
| static if (hasPurePostblit!T) |
| auto success = () @trusted { return alloc.reallocate(m, T.sizeof * initialized); } (); |
| else |
| auto success = alloc.reallocate(m, T.sizeof * initialized); |
| if (success) |
| result = () @trusted { return cast(T[]) m; } (); |
| } |
| |
| return result[0 .. initialized]; |
| } |
| } |
| |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| long[] a = alloc.makeArray!long((int[]).init); |
| assert(a.length == 0 && a.ptr is null); |
| a = alloc.makeArray!long([5, 42]); |
| assert(a.length == 2); |
| assert(a == [ 5, 42]); |
| |
| // we can also infer the type |
| auto b = alloc.makeArray([4.0, 2.0]); |
| static assert(is(typeof(b) == double[])); |
| assert(b == [4.0, 2.0]); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| // infer types for strings |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| auto c = alloc.makeArray("fooπ😜"); |
| static assert(is(typeof(c) == char[])); |
| assert(c == "fooπ😜"); |
| |
| auto d = alloc.makeArray("fooπ😜"d); |
| static assert(is(typeof(d) == dchar[])); |
| assert(d == "fooπ😜"); |
| |
| auto w = alloc.makeArray("fooπ😜"w); |
| static assert(is(typeof(w) == wchar[])); |
| assert(w == "fooπ😜"); |
| } |
| |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| /*pure*/ nothrow @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| import std.internal.test.dummyrange; |
| import std.range : iota; |
| foreach (DummyType; AllDummyRanges) |
| { |
| (alloc) pure nothrow @safe |
| { |
| DummyType d; |
| auto arr = alloc.makeArray(d); |
| assert(arr.length == 10); |
| assert(arr.equal(iota(1, 11))); |
| } (GCAllocator.instance); |
| } |
| } |
| |
| // test failure with a pure, failing struct |
| @safe unittest |
| { |
| import std.exception : assertThrown, enforce; |
| |
| struct NoCopy |
| { |
| int b; |
| |
| @disable this(); |
| |
| this(int b) |
| { |
| this.b = b; |
| } |
| |
| // can't be copied |
| this(this) |
| { |
| enforce(b < 3, "there can only be three elements"); |
| } |
| } |
| import std.experimental.allocator.mallocator : Mallocator; |
| auto arr = [NoCopy(1), NoCopy(2), NoCopy(3)]; |
| assertThrown(makeArray!NoCopy(Mallocator.instance, arr)); |
| |
| struct NoCopyRange |
| { |
| static j = 0; |
| bool empty() |
| { |
| return j > 5; |
| } |
| |
| auto front() |
| { |
| return NoCopy(j); |
| } |
| |
| void popFront() |
| { |
| j++; |
| } |
| } |
| makeArray!NoCopy(Mallocator.instance, NoCopyRange()); // rvalue elements are forwarded/moved |
| } |
| |
| // test failure with an impure, failing struct |
| @system unittest |
| { |
| import std.exception : assertThrown, enforce; |
| |
| static i = 0; |
| static maxElements = 2; |
| struct NoCopy |
| { |
| int val; |
| @disable this(); |
| |
| this(int b){ |
| this.val = i++; |
| } |
| |
| // can't be copied |
| this(this) |
| { |
| enforce(i++ < maxElements, "there can only be four elements"); |
| } |
| } |
| |
| import std.experimental.allocator.mallocator : Mallocator; |
| auto arr = [NoCopy(1), NoCopy(2)]; |
| assertThrown(makeArray!NoCopy(Mallocator.instance, arr)); |
| |
| i = 0; |
| maxElements = 0; // disallow any postblit |
| static j = 0; |
| |
| struct NoCopyRange |
| { |
| bool empty() |
| { |
| return j > 100; |
| } |
| |
| auto front() |
| { |
| return NoCopy(1); |
| } |
| |
| void popFront() |
| { |
| j++; |
| } |
| } |
| |
| auto arr2 = makeArray!NoCopy(Mallocator.instance, NoCopyRange()); |
| assert(i == j && i == 101); // all 101 rvalue elements forwarded/moved |
| } |
| |
| version (StdUnittest) |
| { |
| private struct ForcedInputRange(T) |
| { |
| T[]* array; |
| pure nothrow @safe @nogc: |
| bool empty() { return !array || (*array).empty; } |
| ref T front() { return (*array)[0]; } |
| void popFront() { *array = (*array)[1 .. $]; } |
| } |
| } |
| |
| @system unittest |
| { |
| import std.array : array; |
| import std.range : iota; |
| int[] arr = iota(10).array; |
| |
| void test(A)(auto ref A alloc) |
| { |
| ForcedInputRange!int r; |
| long[] a = alloc.makeArray!long(r); |
| assert(a.length == 0 && a.ptr is null); |
| auto arr2 = arr; |
| r.array = () @trusted { return &arr2; } (); |
| a = alloc.makeArray!long(r); |
| assert(a.length == 10); |
| assert(a == iota(10).array); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| (alloc) pure nothrow @safe { test(alloc); } (GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| /** |
| Grows `array` by appending `delta` more elements. The needed memory is |
| allocated using `alloc`. The extra elements added are either default- |
| initialized, filled with copies of `init`, or initialized with values |
| fetched from `range`. |
| |
| Params: |
| T = element type of the array being created |
| alloc = the allocator used for getting memory |
| array = a reference to the array being grown |
| delta = number of elements to add (upon success the new length of `array` is |
| $(D array.length + delta)) |
| init = element used for filling the array |
| range = range used for initializing the array elements |
| |
| Returns: |
| `true` upon success, `false` if memory could not be allocated. In the |
| latter case `array` is left unaffected. |
| |
| Throws: |
| The first two overloads throw only if `alloc`'s primitives do. The |
| overloads that involve copy initialization deallocate memory and propagate the |
| exception if the copy operation throws. |
| */ |
| bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, |
| size_t delta) |
| { |
| if (!delta) return true; |
| if (array is null) return false; |
| immutable oldLength = array.length; |
| void[] buf = array; |
| if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; |
| array = cast(T[]) buf; |
| array[oldLength .. $].uninitializedFillDefault; |
| return true; |
| } |
| |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| auto arr = alloc.makeArray!int([1, 2, 3]); |
| assert(alloc.expandArray(arr, 3)); |
| assert(arr == [1, 2, 3, 0, 0, 0]); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| test(GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| /// Ditto |
| bool expandArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, |
| size_t delta, auto ref T init) |
| { |
| if (!delta) return true; |
| if (array is null) return false; |
| void[] buf = array; |
| if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; |
| immutable oldLength = array.length; |
| array = cast(T[]) buf; |
| scope(failure) array[oldLength .. $].uninitializedFillDefault; |
| import std.algorithm.mutation : uninitializedFill; |
| array[oldLength .. $].uninitializedFill(init); |
| return true; |
| } |
| |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| auto arr = alloc.makeArray!int([1, 2, 3]); |
| assert(alloc.expandArray(arr, 3, 1)); |
| assert(arr == [1, 2, 3, 1, 1, 1]); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| test(GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| /// Ditto |
| bool expandArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array, |
| R range) |
| if (isInputRange!R) |
| { |
| if (array is null) return false; |
| static if (isForwardRange!R) |
| { |
| immutable delta = walkLength(range.save); |
| if (!delta) return true; |
| immutable oldLength = array.length; |
| |
| // Reallocate support memory |
| void[] buf = array; |
| if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) |
| { |
| return false; |
| } |
| array = cast(T[]) buf; |
| // At this point we're committed to the new length. |
| |
| auto toFill = array[oldLength .. $]; |
| scope (failure) |
| { |
| // Fill the remainder with default-constructed data |
| toFill.uninitializedFillDefault; |
| } |
| |
| for (; !range.empty; range.popFront, toFill = toFill[1 .. $]) |
| { |
| assert(toFill.length > 0); |
| import core.lifetime : emplace; |
| emplace!T(&toFill[0], range.front); |
| } |
| assert(toFill.length == 0); |
| } |
| else |
| { |
| scope(failure) |
| { |
| // The last element didn't make it, fill with default |
| array[$ - 1 .. $].uninitializedFillDefault; |
| } |
| void[] buf = array; |
| for (; !range.empty; range.popFront) |
| { |
| if (!alloc.reallocate(buf, buf.length + T.sizeof)) |
| { |
| array = cast(T[]) buf; |
| return false; |
| } |
| import core.lifetime : emplace; |
| emplace!T(buf[$ - T.sizeof .. $], range.front); |
| } |
| |
| array = cast(T[]) buf; |
| } |
| return true; |
| } |
| |
| /// |
| @system unittest |
| { |
| auto arr = theAllocator.makeArray!int([1, 2, 3]); |
| assert(theAllocator.expandArray(arr, 2)); |
| assert(arr == [1, 2, 3, 0, 0]); |
| import std.range : only; |
| assert(theAllocator.expandArray(arr, only(4, 5))); |
| assert(arr == [1, 2, 3, 0, 0, 4, 5]); |
| } |
| |
| @system unittest |
| { |
| auto arr = theAllocator.makeArray!int([1, 2, 3]); |
| ForcedInputRange!int r; |
| int[] b = [ 1, 2, 3, 4 ]; |
| auto temp = b; |
| r.array = &temp; |
| assert(theAllocator.expandArray(arr, r)); |
| assert(arr == [1, 2, 3, 1, 2, 3, 4]); |
| } |
| |
| // Regression test for https://issues.dlang.org/show_bug.cgi?id=20929 |
| @system unittest |
| { |
| static void test(Char, Allocator)(auto ref Allocator alloc) |
| { |
| auto arr = alloc.makeArray!Char(1, Char('f')); |
| |
| import std.utf : byUTF; |
| auto forwardRange = "oo".byUTF!Char(); |
| static assert(isForwardRange!(typeof(forwardRange))); |
| // Test the forward-range code-path. |
| assert(alloc.expandArray(arr, forwardRange)); |
| |
| assert(arr == "foo"); |
| |
| immutable(Char)[] temp = "bar"; |
| auto inputRange = ForcedInputRange!(immutable(Char))(&temp); |
| // Test the input-range code-path. |
| assert(alloc.expandArray(arr, inputRange)); |
| |
| assert(arr == "foobar"); |
| } |
| |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| test!char(GCAllocator.instance); |
| test!wchar(GCAllocator.instance); |
| test!char(theAllocator); |
| test!wchar(theAllocator); |
| } |
| |
| /** |
| Shrinks an array by `delta` elements. |
| |
| If $(D array.length < delta), does nothing and returns `false`. Otherwise, |
| destroys the last $(D array.length - delta) elements in the array and then |
| reallocates the array's buffer. If reallocation fails, fills the array with |
| default-initialized data. |
| |
| Params: |
| T = element type of the array being created |
| alloc = the allocator used for getting memory |
| array = a reference to the array being shrunk |
| delta = number of elements to remove (upon success the new length of `array` is $(D array.length - delta)) |
| |
| Returns: |
| `true` upon success, `false` if memory could not be reallocated. In the latter |
| case, the slice $(D array[$ - delta .. $]) is left with default-initialized |
| elements. |
| |
| Throws: |
| The first two overloads throw only if `alloc`'s primitives do. The |
| overloads that involve copy initialization deallocate memory and propagate the |
| exception if the copy operation throws. |
| */ |
| bool shrinkArray(T, Allocator)(auto ref Allocator alloc, |
| ref T[] array, size_t delta) |
| { |
| if (delta > array.length) return false; |
| |
| // Destroy elements. If a destructor throws, fill the already destroyed |
| // stuff with the default initializer. |
| { |
| size_t destroyed; |
| scope(failure) |
| { |
| array[$ - delta .. $][0 .. destroyed].uninitializedFillDefault; |
| } |
| foreach (ref e; array[$ - delta .. $]) |
| { |
| e.destroy; |
| ++destroyed; |
| } |
| } |
| |
| if (delta == array.length) |
| { |
| alloc.deallocate(array); |
| array = null; |
| return true; |
| } |
| |
| void[] buf = array; |
| if (!alloc.reallocate(buf, buf.length - T.sizeof * delta)) |
| { |
| // urgh, at least fill back with default |
| array[$ - delta .. $].uninitializedFillDefault; |
| return false; |
| } |
| array = cast(T[]) buf; |
| return true; |
| } |
| |
| /// |
| @system unittest |
| { |
| int[] a = theAllocator.makeArray!int(100, 42); |
| assert(a.length == 100); |
| assert(theAllocator.shrinkArray(a, 98)); |
| assert(a.length == 2); |
| assert(a == [42, 42]); |
| } |
| |
| @system unittest |
| { |
| void test(A)(auto ref A alloc) |
| { |
| long[] a = alloc.makeArray!long((int[]).init); |
| assert(a.length == 0 && a.ptr is null); |
| a = alloc.makeArray!long(100, 42); |
| assert(alloc.shrinkArray(a, 98)); |
| assert(a.length == 2); |
| assert(a == [ 42, 42]); |
| } |
| import std.experimental.allocator.gc_allocator : GCAllocator; |
| test(GCAllocator.instance); |
| test(theAllocator); |
| } |
| |
| /** |
| |
| Destroys and then deallocates (using `alloc`) the object pointed to by a |
| pointer, the class object referred to by a `class` or `interface` |
| reference, or an entire array. It is assumed the respective entities had been |
| allocated with the same allocator. |
| |
| */ |
| void dispose(A, T)(auto ref A alloc, auto ref T* p) |
| { |
| static if (hasElaborateDestructor!T) |
| { |
| destroy(*p); |
| } |
| alloc.deallocate((cast(void*) p)[0 .. T.sizeof]); |
| static if (__traits(isRef, p)) |
| p = null; |
| } |
| |
| /// Ditto |
| void dispose(A, T)(auto ref A alloc, auto ref T p) |
| if (is(T == class) || is(T == interface)) |
| { |
| if (!p) return; |
| static if (is(T == interface)) |
| { |
| version (Windows) |
| { |
| import core.sys.windows.unknwn : IUnknown; |
| static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in " |
| ~ __PRETTY_FUNCTION__); |
| } |
| auto ob = cast(Object) p; |
| } |
| else |
| alias ob = p; |
| auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; |
| destroy(p); |
| alloc.deallocate(support); |
| static if (__traits(isRef, p)) |
| p = null; |
| } |
| |
| /// Ditto |
| void dispose(A, T)(auto ref A alloc, auto ref T[] array) |
| { |
| static if (hasElaborateDestructor!(typeof(array[0]))) |
| { |
| foreach (ref e; array) |
| { |
| destroy(e); |
| } |
| } |
| alloc.deallocate(array); |
| static if (__traits(isRef, array)) |
| array = null; |
| } |
| |
| @system unittest |
| { |
| static int x; |
| static interface I |
| { |
| void method(); |
| } |
| static class A : I |
| { |
| int y; |
| override void method() { x = 21; } |
| ~this() { x = 42; } |
| } |
| static class B : A |
| { |
| } |
| auto a = theAllocator.make!A; |
| a.method(); |
| assert(x == 21); |
| theAllocator.dispose(a); |
| assert(x == 42); |
| |
| B b = theAllocator.make!B; |
| b.method(); |
| assert(x == 21); |
| theAllocator.dispose(b); |
| assert(x == 42); |
| |
| I i = theAllocator.make!B; |
| i.method(); |
| assert(x == 21); |
| theAllocator.dispose(i); |
| assert(x == 42); |
| |
| int[] arr = theAllocator.makeArray!int(43); |
| theAllocator.dispose(arr); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=16512 |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| int* i = Mallocator.instance.make!int(0); |
| Mallocator.instance.dispose(i); |
| assert(i is null); |
| |
| Object o = Mallocator.instance.make!Object(); |
| Mallocator.instance.dispose(o); |
| assert(o is null); |
| |
| uint* u = Mallocator.instance.make!uint(0); |
| Mallocator.instance.dispose((){return u;}()); |
| assert(u !is null); |
| |
| uint[] ua = Mallocator.instance.makeArray!uint([0,1,2]); |
| Mallocator.instance.dispose(ua); |
| assert(ua is null); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=15721 |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| interface Foo {} |
| class Bar: Foo {} |
| |
| Bar bar; |
| Foo foo; |
| bar = Mallocator.instance.make!Bar; |
| foo = cast(Foo) bar; |
| Mallocator.instance.dispose(foo); |
| } |
| |
| /** |
| Allocates a multidimensional array of elements of type T. |
| |
| Params: |
| N = number of dimensions |
| T = element type of an element of the multidimensional arrat |
| alloc = the allocator used for getting memory |
| lengths = static array containing the size of each dimension |
| |
| Returns: |
| An N-dimensional array with individual elements of type T. |
| */ |
| auto makeMultidimensionalArray(T, Allocator, size_t N)(auto ref Allocator alloc, size_t[N] lengths...) |
| { |
| static if (N == 1) |
| { |
| return makeArray!T(alloc, lengths[0]); |
| } |
| else |
| { |
| alias E = typeof(makeMultidimensionalArray!(T, Allocator, N - 1)(alloc, lengths[1 .. $])); |
| auto ret = makeArray!E(alloc, lengths[0]); |
| foreach (ref e; ret) |
| e = makeMultidimensionalArray!(T, Allocator, N - 1)(alloc, lengths[1 .. $]); |
| return ret; |
| } |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| auto mArray = Mallocator.instance.makeMultidimensionalArray!int(2, 3, 6); |
| |
| // deallocate when exiting scope |
| scope(exit) |
| { |
| Mallocator.instance.disposeMultidimensionalArray(mArray); |
| } |
| |
| assert(mArray.length == 2); |
| foreach (lvl2Array; mArray) |
| { |
| assert(lvl2Array.length == 3); |
| foreach (lvl3Array; lvl2Array) |
| assert(lvl3Array.length == 6); |
| } |
| } |
| |
| /** |
| Destroys and then deallocates a multidimensional array, assuming it was |
| created with makeMultidimensionalArray and the same allocator was used. |
| |
| Params: |
| T = element type of an element of the multidimensional array |
| alloc = the allocator used for getting memory |
| array = the multidimensional array that is to be deallocated |
| */ |
| void disposeMultidimensionalArray(T, Allocator)(auto ref Allocator alloc, auto ref T[] array) |
| { |
| static if (isArray!T) |
| { |
| foreach (ref e; array) |
| disposeMultidimensionalArray(alloc, e); |
| } |
| |
| dispose(alloc, array); |
| static if (__traits(isRef, array)) |
| array = null; |
| } |
| |
| /// |
| @system unittest |
| { |
| struct TestAllocator |
| { |
| import std.experimental.allocator.common : platformAlignment; |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| alias allocator = Mallocator.instance; |
| |
| private static struct ByteRange |
| { |
| void* ptr; |
| size_t length; |
| } |
| |
| private ByteRange[] _allocations; |
| |
| enum uint alignment = platformAlignment; |
| |
| void[] allocate(size_t numBytes) |
| { |
| auto ret = allocator.allocate(numBytes); |
| _allocations ~= ByteRange(ret.ptr, ret.length); |
| return ret; |
| } |
| |
| bool deallocate(void[] bytes) |
| { |
| import std.algorithm.mutation : remove; |
| import std.algorithm.searching : canFind; |
| |
| bool pred(ByteRange other) |
| { return other.ptr == bytes.ptr && other.length == bytes.length; } |
| |
| assert(_allocations.canFind!pred); |
| |
| _allocations = _allocations.remove!pred; |
| return allocator.deallocate(bytes); |
| } |
| |
| ~this() |
| { |
| assert(!_allocations.length); |
| } |
| } |
| |
| TestAllocator allocator; |
| |
| auto mArray = allocator.makeMultidimensionalArray!int(2, 3, 5, 6, 7, 2); |
| |
| allocator.disposeMultidimensionalArray(mArray); |
| } |
| |
| /** |
| |
| Returns a dynamically-typed `CAllocator` built around a given statically- |
| typed allocator `a` of type `A`. Passing a pointer to the allocator |
| creates a dynamic allocator around the allocator pointed to by the pointer, |
| without attempting to copy or move it. Passing the allocator by value or |
| reference behaves as follows. |
| |
| $(UL |
| $(LI If `A` has no state, the resulting object is allocated in static |
| shared storage.) |
| $(LI If `A` has state, the result will $(REF move, std,algorithm,mutation) |
| the supplied allocator $(D A a) within. The result itself is allocated in its |
| own statically-typed allocator.) |
| ) |
| |
| */ |
| RCIAllocator allocatorObject(A)(auto ref A a) |
| if (!isPointer!A) |
| { |
| import core.lifetime : emplace; |
| static if (stateSize!A == 0) |
| { |
| enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); |
| __gshared ulong[s] state; |
| __gshared RCIAllocator result; |
| if (result.isNull) |
| { |
| // Don't care about a few races |
| result = RCIAllocator(emplace!(CAllocatorImpl!A)(state[])); |
| } |
| assert(!result.isNull); |
| return result; |
| } |
| else |
| { |
| auto state = a.allocate(stateSize!(CAllocatorImpl!A)); |
| import std.algorithm.mutation : move; |
| import std.traits : hasMember; |
| static if (hasMember!(A, "deallocate")) |
| { |
| scope(failure) a.deallocate(state); |
| } |
| auto tmp = cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state); |
| move(a, tmp.impl); |
| return RCIAllocator(tmp); |
| } |
| } |
| |
| /// Ditto |
| RCIAllocator allocatorObject(A)(A* pa) |
| { |
| assert(pa); |
| import core.lifetime : emplace; |
| auto state = pa.allocate(stateSize!(CAllocatorImpl!(A, Yes.indirect))); |
| import std.traits : hasMember; |
| static if (hasMember!(A, "deallocate")) |
| { |
| scope(failure) pa.deallocate(state); |
| } |
| return RCIAllocator(emplace!(CAllocatorImpl!(A, Yes.indirect)) |
| (state, pa)); |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.experimental.allocator.mallocator : Mallocator; |
| |
| RCIAllocator a = allocatorObject(Mallocator.instance); |
| auto b = a.allocate(100); |
| assert(b.length == 100); |
| assert(a.deallocate(b)); |
| |
| // The in-situ region must be used by pointer |
| import std.experimental.allocator.building_blocks.region : InSituRegion; |
| auto r = InSituRegion!1024(); |
| a = allocatorObject(&r); |
| b = a.allocate(200); |
| assert(b.length == 200); |
| // In-situ regions can deallocate the last allocation |
| assert(a.deallocate(b)); |
| } |
| |
| @system unittest |
| { |
| import std.conv; |
| import std.experimental.allocator.mallocator; |
| import std.experimental.allocator.building_blocks.stats_collector; |
| |
| alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); |
| SCAlloc statsCollectorAlloc; |
| assert(statsCollectorAlloc.bytesUsed == 0); |
| |
| auto _allocator = allocatorObject(statsCollectorAlloc); |
| // Ensure that the allocator was passed through in CAllocatorImpl |
| // This allocator was used to allocate the chunk that holds the |
| // CAllocatorImpl object; which is it's own wrapper |
| assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed |
| == stateSize!(CAllocatorImpl!(SCAlloc))); |
| _allocator.allocate(1); |
| assert((cast(CAllocatorImpl!(SCAlloc))(_allocator._alloc)).impl.bytesUsed |
| == stateSize!(CAllocatorImpl!(SCAlloc)) + 1); |
| } |
| |
| /** |
| |
| Returns a dynamically-typed `CSharedAllocator` built around a given statically- |
| typed allocator `a` of type `A`. Passing a pointer to the allocator |
| creates a dynamic allocator around the allocator pointed to by the pointer, |
| without attempting to copy or move it. Passing the allocator by value or |
| reference behaves as follows. |
| |
| $(UL |
| $(LI If `A` has no state, the resulting object is allocated in static |
| shared storage.) |
| $(LI If `A` has state and is copyable, the result will |
| $(REF move, std,algorithm,mutation) the supplied allocator $(D A a) within. |
| The result itself is allocated in its own statically-typed allocator.) |
| $(LI If `A` has state and is not copyable, the result will move the |
| passed-in argument into the result. The result itself is allocated in its own |
| statically-typed allocator.) |
| ) |
| |
| */ |
| //nothrow @safe |
| //nothrow @nogc @safe |
| nothrow |
| RCISharedAllocator sharedAllocatorObject(A)(auto ref A a) |
| if (!isPointer!A) |
| { |
| import core.lifetime : emplace; |
| static if (stateSize!A == 0) |
| { |
| enum s = stateSize!(CSharedAllocatorImpl!A).divideRoundUp(ulong.sizeof); |
| static shared ulong[s] state; |
| static RCISharedAllocator result; |
| if (result.isNull) |
| { |
| // Don't care about a few races |
| result = RCISharedAllocator( |
| (cast(shared CSharedAllocatorImpl!A)( |
| emplace!(CSharedAllocatorImpl!A)( |
| (() @trusted => cast(ulong[]) state[])())))); |
| } |
| assert(!result.isNull); |
| return result; |
| } |
| else static if (is(typeof({ shared A b = a; shared A c = b; }))) // copyable |
| { |
| auto state = a.allocate(stateSize!(CSharedAllocatorImpl!A)); |
| import std.algorithm.mutation : move; |
| import std.traits : hasMember; |
| static if (hasMember!(A, "deallocate")) |
| { |
| scope(failure) a.deallocate(state); |
| } |
| auto tmp = emplace!(shared CSharedAllocatorImpl!A)(state); |
| move(a, tmp.impl); |
| return RCISharedAllocator(tmp); |
| } |
| else // the allocator object is not copyable |
| { |
| assert(0, "Not yet implemented"); |
| } |
| } |
| |
| /// Ditto |
| RCISharedAllocator sharedAllocatorObject(A)(A* pa) |
| { |
| assert(pa); |
| import core.lifetime : emplace; |
| auto state = pa.allocate(stateSize!(CSharedAllocatorImpl!(A, Yes.indirect))); |
| import std.traits : hasMember; |
| static if (hasMember!(A, "deallocate")) |
| { |
| scope(failure) pa.deallocate(state); |
| } |
| return RCISharedAllocator(emplace!(shared CSharedAllocatorImpl!(A, Yes.indirect))(state, pa)); |
| } |
| |
| |
| /** |
| |
| Implementation of `IAllocator` using `Allocator`. This adapts a |
| statically-built allocator type to `IAllocator` that is directly usable by |
| non-templated code. |
| |
| Usually `CAllocatorImpl` is used indirectly by calling $(LREF theAllocator). |
| */ |
| class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) |
| : IAllocator |
| { |
| import std.traits : hasMember; |
| |
| static if (stateSize!Allocator) private size_t rc = 1; |
| |
| /** |
| The implementation is available as a public member. |
| */ |
| static if (indirect) |
| { |
| nothrow: |
| private Allocator* pimpl; |
| |
| @nogc pure @safe |
| ref Allocator impl() |
| { |
| return *pimpl; |
| } |
| |
| @nogc pure @safe |
| this(Allocator* pa) |
| { |
| pimpl = pa; |
| } |
| } |
| else |
| { |
| static if (stateSize!Allocator) Allocator impl; |
| else alias impl = Allocator.instance; |
| } |
| |
| nothrow: |
| /// Returns `impl.alignment`. |
| override @property uint alignment() |
| { |
| return impl.alignment; |
| } |
| |
| /** |
| Returns `impl.goodAllocSize(s)`. |
| */ |
| override size_t goodAllocSize(size_t s) |
| { |
| return impl.goodAllocSize(s); |
| } |
| |
| /** |
| Returns `impl.allocate(s)`. |
| */ |
| override void[] allocate(size_t s, TypeInfo ti = null) |
| { |
| return impl.allocate(s); |
| } |
| |
| /** |
| If `impl.alignedAllocate` exists, calls it and returns the result. |
| Otherwise, always returns `null`. |
| */ |
| override void[] alignedAllocate(size_t s, uint a) |
| { |
| static if (hasMember!(Allocator, "alignedAllocate")) |
| return impl.alignedAllocate(s, a); |
| else |
| return null; |
| } |
| |
| /** |
| If `Allocator` implements `owns`, forwards to it. Otherwise, returns |
| `Ternary.unknown`. |
| */ |
| override Ternary owns(void[] b) |
| { |
| static if (hasMember!(Allocator, "owns")) return impl.owns(b); |
| else return Ternary.unknown; |
| } |
| |
| /// Returns $(D impl.expand(b, s)) if defined, `false` otherwise. |
| override bool expand(ref void[] b, size_t s) |
| { |
| static if (hasMember!(Allocator, "expand")) |
| return impl.expand(b, s); |
| else |
| return s == 0; |
| } |
| |
| /// Returns $(D impl.reallocate(b, s)). |
| override bool reallocate(ref void[] b, size_t s) |
| { |
| return impl.reallocate(b, s); |
| } |
| |
| /// Forwards to `impl.alignedReallocate` if defined, `false` otherwise. |
| bool alignedReallocate(ref void[] b, size_t s, uint a) |
| { |
| static if (!hasMember!(Allocator, "alignedAllocate")) |
| { |
| return false; |
| } |
| else |
| { |
| return impl.alignedReallocate(b, s, a); |
| } |
| } |
| |
| // Undocumented for now |
| Ternary resolveInternalPointer(const void* p, ref void[] result) |
| { |
| static if (hasMember!(Allocator, "resolveInternalPointer")) |
| { |
| return impl.resolveInternalPointer(p, result); |
| } |
| else |
| { |
| return Ternary.unknown; |
| } |
| } |
| |
| /** |
| If `impl.deallocate` is not defined, returns `false`. Otherwise it forwards |
| the call. |
| */ |
| override bool deallocate(void[] b) |
| { |
| static if (hasMember!(Allocator, "deallocate")) |
| { |
| return impl.deallocate(b); |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| Calls `impl.deallocateAll()` and returns the result if defined, |
| otherwise returns `false`. |
| */ |
| override bool deallocateAll() |
| { |
| static if (hasMember!(Allocator, "deallocateAll")) |
| { |
| return impl.deallocateAll(); |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| Forwards to `impl.empty()` if defined, otherwise returns `Ternary.unknown`. |
| */ |
| override Ternary empty() |
| { |
| static if (hasMember!(Allocator, "empty")) |
| { |
| return Ternary(impl.empty); |
| } |
| else |
| { |
| return Ternary.unknown; |
| } |
| } |
| |
| /** |
| Returns `impl.allocateAll()` if present, `null` otherwise. |
| */ |
| override void[] allocateAll() |
| { |
| static if (hasMember!(Allocator, "allocateAll")) |
| { |
| return impl.allocateAll(); |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| @nogc nothrow pure @safe |
| override void incRef() |
| { |
| static if (stateSize!Allocator) ++rc; |
| } |
| |
| @nogc nothrow pure @trusted |
| override bool decRef() |
| { |
| static if (stateSize!Allocator) |
| { |
| import core.stdc.string : memcpy; |
| |
| if (rc == 1) |
| { |
| static if (indirect) |
| { |
| Allocator* tmp = pimpl; |
| } |
| else |
| { |
| Allocator tmp; |
| memcpy(&tmp, &this.impl, Allocator.sizeof); |
| } |
| void[] support = (cast(void*) this)[0 .. stateSize!(typeof(this))]; |
| tmp.deallocate(support); |
| return false; |
| } |
| |
| --rc; |
| return true; |
| } |
| else |
| { |
| return true; |
| } |
| } |
| } |
| |
| /** |
| |
| Implementation of `ISharedAllocator` using `Allocator`. This adapts a |
| statically-built, shareable across threads, allocator type to `ISharedAllocator` |
| that is directly usable by non-templated code. |
| |
| Usually `CSharedAllocatorImpl` is used indirectly by calling |
| $(LREF processAllocator). |
| */ |
| class CSharedAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) |
| : ISharedAllocator |
| { |
| import std.traits : hasMember; |
| import core.atomic : atomicOp, atomicLoad; |
| |
| static if (stateSize!Allocator) shared size_t rc = 1; |
| |
| /** |
| The implementation is available as a public member. |
| */ |
| static if (indirect) |
| { |
| nothrow: |
| private shared Allocator* pimpl; |
| |
| @nogc pure @safe |
| ref Allocator impl() shared |
| { |
| return *pimpl; |
| } |
| |
| @nogc pure @safe |
| this(Allocator* pa) shared |
| { |
| pimpl = pa; |
| } |
| } |
| else |
| { |
| static if (stateSize!Allocator) shared Allocator impl; |
| else alias impl = Allocator.instance; |
| } |
| |
| nothrow: |
|