Make allocator shared and fix some RefCounted bugs

This commit is contained in:
2016-12-06 21:29:08 +01:00
parent b3fdd6fd4a
commit fa607141e4
19 changed files with 2682 additions and 2717 deletions

View File

@ -10,214 +10,42 @@
*/
module tanya.memory.allocator;
import std.algorithm.mutation;
import std.experimental.allocator;
import std.typecons;
/**
* Abstract class implementing a basic allocator.
*/
abstract class Allocator : IAllocator
interface Allocator
{
/**
* Not supported.
*
* Returns: $(D_KEYWORD false).
*/
bool deallocateAll() const @nogc @safe pure nothrow
{
return false;
}
@nogc:
@property uint alignment() const shared pure nothrow @safe;
/**
* Not supported.
*
* Returns $(D_PSYMBOL Ternary.unknown).
*/
Ternary empty() const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size, TypeInfo ti = null) shared nothrow @safe;
/**
* Not supported.
*
* Params:
* b = Memory block.
*
* Returns: $(D_PSYMBOL Ternary.unknown).
*/
Ternary owns(void[] b) const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared nothrow @safe;
/**
* Not supported.
*
* Params:
* p = Pointer to a memory block.
* result = Full block allocated.
*
* Returns: $(D_PSYMBOL Ternary.unknown).
*/
Ternary resolveInternalPointer(void* p, ref void[] result)
const @nogc @safe pure nothrow
{
return Ternary.unknown;
}
/**
* Params:
* size = Amount of memory to allocate.
*
* Returns: The good allocation size that guarantees zero internal
* fragmentation.
*/
size_t goodAllocSize(size_t s)
{
auto rem = s % alignment;
return rem ? s + alignment - rem : s;
}
/**
* Not supported.
*
* Returns: $(D_KEYWORD null).
*
*/
void[] allocateAll() const @nogc @safe pure nothrow
{
return null;
}
/**
* Not supported.
*
* Params:
* b = Block to be expanded.
* s = New size.
*
* Returns: $(D_KEYWORD false).
*/
bool expand(ref void[] b, size_t s) const @nogc @safe pure nothrow
{
return false;
}
/**
* Not supported.
*
* Params:
* n = Amount of memory to allocate.
* a = Alignment.
*
* Returns: $(D_KEYWORD null).
*/
void[] alignedAllocate(size_t n, uint a) const @nogc @safe pure nothrow
{
return null;
}
/**
* Not supported.
*
* Params:
* n = Amount of memory to allocate.
* a = Alignment.
*
* Returns: $(D_KEYWORD false).
*/
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
const @nogc @safe pure nothrow
{
return false;
}
}
/**
* Params:
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
* init = The value to fill the new part of the array with if it becomes
* larger.
*
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter
*/
bool resizeArray(T)(IAllocator allocator,
ref T[] array,
in size_t length,
T init = T.init)
{
void[] buf = array;
immutable oldLength = array.length;
if (!allocator.reallocate(buf, length * T.sizeof))
{
return false;
}
array = cast(T[]) buf;
if (oldLength < length)
{
array[oldLength .. $].uninitializedFill(init);
}
return true;
}
///
unittest
{
int[] p;
theAllocator.resizeArray(p, 20);
assert(p.length == 20);
theAllocator.resizeArray(p, 30);
assert(p.length == 30);
theAllocator.resizeArray(p, 10);
assert(p.length == 10);
theAllocator.resizeArray(p, 0);
assert(p is null);
}
/**
* Mixin to get around the impossibility to define a default constructor for
* structs. It can be used for the structs that don't disable the default
* constructor and don't wan't to force passing the allocator each time to
* the constructor.
*
* It defines the private property `allocator`, a constructor that accepts only
* an allocator instance and the method `checkAllocator` that checks if an
* allocator is set and sets it to ` $(D_PSYMBOL theAllocator) if not.
*
* `checkAllocator` should be used at beginning of functions that
* allocate/free memory.
*/
mixin template StructAllocator()
{
private IAllocator allocator;
this(IAllocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
}
pragma(inline, true)
private void checkAllocator() nothrow @safe @nogc
{
if (allocator is null)
{
allocator = theAllocator;
}
}
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) shared nothrow @safe;
}

View File

@ -16,14 +16,14 @@ import core.exception;
version (Posix)
{
import core.stdc.errno;
import core.sys.posix.sys.mman;
import core.sys.posix.unistd;
import core.stdc.errno;
import core.sys.posix.sys.mman;
import core.sys.posix.unistd;
}
else version (Windows)
{
import core.sys.windows.winbase;
import core.sys.windows.windows;
import core.sys.windows.winbase;
import core.sys.windows.windows;
}
/**
@ -49,437 +49,436 @@ else version (Windows)
* --------------------------------------------------- ------------------------
*
* TODO:
* $(UL
* $(LI Thread safety (core.atomic.cas))
* $(LI If two neighbour blocks are free, they can be merged)
* $(LI Reallocation shoud check if there is enough free space in the
* next block instead of always moving the memory)
* $(LI Make 64 KB regions mininmal region size on Linux)
* )
* $(UL
* $(LI Thread safety (core.atomic.cas))
* $(LI If two neighbour blocks are free, they can be merged)
* $(LI Reallocation shoud check if there is enough free space in the
* next block instead of always moving the memory)
* $(LI Make 64 KB regions mininmal region size on Linux)
* )
*/
class MmapPool : Allocator
{
@disable this();
@nogc:
shared static this()
{
version (Posix)
{
pageSize = sysconf(_SC_PAGE_SIZE);
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
}
shared static this()
{
version (Posix)
{
pageSize = sysconf(_SC_PAGE_SIZE);
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
}
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size, TypeInfo ti = null) shared nothrow @trusted
{
if (!size)
{
return null;
}
immutable dataSize = addAlignment(size);
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size, TypeInfo ti = null) @nogc @trusted nothrow
{
if (!size)
{
return null;
}
immutable dataSize = addAlignment(size);
void* data = findBlock(dataSize);
if (data is null)
{
data = initializeRegion(dataSize);
}
void* data = findBlock(dataSize);
if (data is null)
{
data = initializeRegion(dataSize);
}
return data is null ? null : data[0..size];
}
return data is null ? null : data[0..size];
}
///
@safe nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
///
@nogc @safe nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(p);
assert(p);
MmapPool.instance.deallocate(p);
}
MmapPool.instance.deallocate(p);
}
/**
* Search for a block large enough to keep $(D_PARAM size) and split it
* into two blocks if the block is too large.
*
* Params:
* size = Minimum size the block should have.
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(size_t size) shared nothrow
{
Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next)
{
block1 = cast(Block) (cast(void*) r + regionEntrySize);
do
{
if (block1.free && block1.size >= size)
{
break RegionLoop;
}
}
while ((block1 = block1.next) !is null);
}
if (block1 is null)
{
return null;
}
else if (block1.size >= size + alignment + blockEntrySize)
{ // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
block2.prev = block1;
if (block1.next is null)
{
block2.next = null;
}
else
{
block2.next = block1.next.next;
}
block1.next = block2;
/**
* Search for a block large enough to keep $(D_PARAM size) and split it
* into two blocks if the block is too large.
*
* Params:
* size = Minimum size the block should have.
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(size_t size) @nogc nothrow
{
Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next)
{
block1 = cast(Block) (cast(void*) r + regionEntrySize);
do
{
if (block1.free && block1.size >= size)
{
break RegionLoop;
}
}
while ((block1 = block1.next) !is null);
}
if (block1 is null)
{
return null;
}
else if (block1.size >= size + alignment + blockEntrySize)
{ // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
block2.prev = block1;
if (block1.next is null)
{
block2.next = null;
}
else
{
block2.next = block1.next.next;
}
block1.next = block2;
block1.free = false;
block2.free = true;
block1.free = false;
block2.free = true;
block2.size = block1.size - blockEntrySize - size;
block1.size = size;
block2.size = block1.size - blockEntrySize - size;
block1.size = size;
block2.region = block1.region;
atomicOp!"+="(block1.region.blocks, 1);
}
else
{
block1.free = false;
atomicOp!"+="(block1.region.blocks, 1);
}
return cast(void*) block1 + blockEntrySize;
}
block2.region = block1.region;
atomicOp!"+="(block1.region.blocks, 1);
}
else
{
block1.free = false;
atomicOp!"+="(block1.region.blocks, 1);
}
return cast(void*) block1 + blockEntrySize;
}
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared nothrow @trusted
{
if (p is null)
{
return true;
}
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) @nogc @trusted nothrow
{
if (p is null)
{
return true;
}
Block block = cast(Block) (p.ptr - blockEntrySize);
if (block.region.blocks <= 1)
{
if (block.region.prev !is null)
{
block.region.prev.next = block.region.next;
}
else // Replace the list head. It is being deallocated
{
head = block.region.next;
}
if (block.region.next !is null)
{
block.region.next.prev = block.region.prev;
}
version (Posix)
{
return munmap(cast(void*) block.region, block.region.size) == 0;
}
version (Windows)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
}
}
else
{
block.free = true;
atomicOp!"-="(block.region.blocks, 1);
return true;
}
}
Block block = cast(Block) (p.ptr - blockEntrySize);
if (block.region.blocks <= 1)
{
if (block.region.prev !is null)
{
block.region.prev.next = block.region.next;
}
else // Replace the list head. It is being deallocated
{
head = block.region.next;
}
if (block.region.next !is null)
{
block.region.next.prev = block.region.prev;
}
version (Posix)
{
return munmap(cast(void*) block.region, block.region.size) == 0;
}
version (Windows)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
}
}
else
{
block.free = true;
atomicOp!"-="(block.region.blocks, 1);
return true;
}
}
///
@safe nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
///
@nogc @safe nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(MmapPool.instance.deallocate(p));
}
assert(MmapPool.instance.deallocate(p));
}
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) shared nothrow @trusted
{
void[] reallocP;
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow
{
void[] reallocP;
if (size == p.length)
{
return true;
}
else if (size > 0)
{
reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
}
if (size == p.length)
{
return true;
}
else if (size > 0)
{
reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
}
if (p !is null)
{
if (size > p.length)
{
reallocP[0..p.length] = p[0..$];
}
else if (size > 0)
{
reallocP[0..size] = p[0..size];
}
deallocate(p);
}
p = reallocP;
if (p !is null)
{
if (size > p.length)
{
reallocP[0..p.length] = p[0..$];
}
else if (size > 0)
{
reallocP[0..size] = p[0..size];
}
deallocate(p);
}
p = reallocP;
return true;
}
return true;
}
///
nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
(cast(int[]) p)[7] = 123;
///
@nogc nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
(cast(int[]) p)[7] = 123;
assert(p.length == 40);
assert(p.length == 40);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 20 * int.sizeof);
(cast(int[]) p)[15] = 8;
MmapPool.instance.reallocate(p, 20 * int.sizeof);
(cast(int[]) p)[15] = 8;
assert(p.length == 80);
assert((cast(int[]) p)[15] == 8);
assert((cast(int[]) p)[7] == 123);
assert(p.length == 80);
assert((cast(int[]) p)[15] == 8);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.deallocate(p);
}
MmapPool.instance.deallocate(p);
}
/**
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref shared(MmapPool) instance() nothrow @trusted
{
if (instance_ is null)
{
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
/**
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref MmapPool instance() @nogc @trusted nothrow
{
if (instance_ is null)
{
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
Region head; // Will become soon our region list head
void* data = initializeRegion(instanceSize, head);
if (data !is null)
{
data[0..instanceSize] = typeid(MmapPool).initializer[];
instance_ = cast(shared MmapPool) data;
instance_.head = head;
}
}
return instance_;
}
Region head; // Will become soon our region list head
void* data = initializeRegion(instanceSize, head);
if (data !is null)
{
data[0..instanceSize] = typeid(MmapPool).initializer[];
instance_ = cast(MmapPool) data;
instance_.head = head;
}
}
return instance_;
}
///
@safe nothrow unittest
{
assert(instance is instance);
}
///
@nogc @safe nothrow unittest
{
assert(instance is instance);
}
/**
* Initializes a region for one element.
*
* Params:
* size = Aligned size of the first data block in the region.
* head = Region list head.
*
* Returns: A pointer to the data.
*/
private static void* initializeRegion(size_t size,
ref Region head) nothrow
{
immutable regionSize = calculateRegionSize(size);
version (Posix)
{
void* p = mmap(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{
if (errno == ENOMEM)
{
onOutOfMemoryError();
}
return null;
}
}
else version (Windows)
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
if (p is null)
{
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
{
onOutOfMemoryError();
}
return null;
}
}
/**
* Initializes a region for one element.
*
* Params:
* size = Aligned size of the first data block in the region.
* head = Region list head.
*
* Returns: A pointer to the data.
*/
private static void* initializeRegion(size_t size,
ref Region head) @nogc nothrow
{
immutable regionSize = calculateRegionSize(size);
version (Posix)
{
void* p = mmap(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{
if (errno == ENOMEM)
{
onOutOfMemoryError();
}
return null;
}
}
else version (Windows)
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
if (p is null)
{
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
{
onOutOfMemoryError();
}
return null;
}
}
Region region = cast(Region) p;
region.blocks = 1;
region.size = regionSize;
Region region = cast(Region) p;
region.blocks = 1;
region.size = regionSize;
// Set the pointer to the head of the region list
if (head !is null)
{
head.prev = region;
}
region.next = head;
region.prev = null;
head = region;
// Set the pointer to the head of the region list
if (head !is null)
{
head.prev = region;
}
region.next = head;
region.prev = null;
head = region;
// Initialize the data block
void* memoryPointer = p + regionEntrySize;
Block block1 = cast(Block) memoryPointer;
block1.size = size;
block1.free = false;
// Initialize the data block
void* memoryPointer = p + regionEntrySize;
Block block1 = cast(Block) memoryPointer;
block1.size = size;
block1.free = false;
// It is what we want to return
void* data = memoryPointer + blockEntrySize;
// It is what we want to return
void* data = memoryPointer + blockEntrySize;
// Free block after data
memoryPointer = data + size;
Block block2 = cast(Block) memoryPointer;
block1.prev = block2.next = null;
block1.next = block2;
block2.prev = block1;
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
block2.free = true;
block1.region = block2.region = region;
// Free block after data
memoryPointer = data + size;
Block block2 = cast(Block) memoryPointer;
block1.prev = block2.next = null;
block1.next = block2;
block2.prev = block1;
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
block2.free = true;
block1.region = block2.region = region;
return data;
}
return data;
}
/// Ditto.
private void* initializeRegion(size_t size) shared nothrow
{
return initializeRegion(size, head);
}
/// Ditto.
private void* initializeRegion(size_t size) @nogc nothrow
{
return initializeRegion(size, head);
}
/**
* Params:
* x = Space to be aligned.
*
* Returns: Aligned size of $(D_PARAM x).
*/
pragma(inline)
private static immutable(size_t) addAlignment(size_t x)
@safe pure nothrow
out (result)
{
assert(result > 0);
}
body
{
return (x - 1) / alignment_ * alignment_ + alignment_;
}
/**
* Params:
* x = Space to be aligned.
*
* Returns: Aligned size of $(D_PARAM x).
*/
pragma(inline)
private static immutable(size_t) addAlignment(size_t x)
@nogc @safe pure nothrow
out (result)
{
assert(result > 0);
}
body
{
return (x - 1) / alignment_ * alignment_ + alignment_;
}
/**
* Params:
* x = Required space.
*
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/
pragma(inline)
private static immutable(size_t) calculateRegionSize(size_t x)
@safe pure nothrow
out (result)
{
assert(result > 0);
}
body
{
x += regionEntrySize + blockEntrySize * 2;
return x / pageSize * pageSize + pageSize;
}
/**
* Params:
* x = Required space.
*
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/
pragma(inline)
private static immutable(size_t) calculateRegionSize(size_t x)
@nogc @safe pure nothrow
out (result)
{
assert(result > 0);
}
body
{
x += regionEntrySize + blockEntrySize * 2;
return x / pageSize * pageSize + pageSize;
}
@property uint alignment() shared const pure nothrow @safe
{
return alignment_;
}
private enum alignment_ = 8;
@property uint alignment() const @nogc @safe pure nothrow
{
return alignment_;
}
private enum alignment_ = 8;
private static shared MmapPool instance_;
private static MmapPool instance_;
private shared static immutable size_t pageSize;
private shared static immutable size_t pageSize;
private shared struct RegionEntry
{
Region prev;
Region next;
uint blocks;
size_t size;
}
private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared struct RegionEntry
{
Region prev;
Region next;
uint blocks;
size_t size;
}
private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared Region head;
private shared Region head;
private shared struct BlockEntry
{
Block prev;
Block next;
bool free;
size_t size;
Region region;
}
private alias Block = shared BlockEntry*;
private enum blockEntrySize = 40;
private shared struct BlockEntry
{
Block prev;
Block next;
bool free;
size_t size;
Region region;
}
private alias Block = shared BlockEntry*;
private enum blockEntrySize = 40;
}

View File

@ -10,6 +10,75 @@
*/
module tanya.memory;
import std.algorithm.mutation;
public import std.experimental.allocator;
public import tanya.memory.allocator;
public import tanya.memory.types;
shared Allocator allocator;
shared static this() nothrow @safe @nogc
{
import tanya.memory.mmappool;
allocator = MmapPool.instance;
}
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
{
return allocator;
}
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
{
.allocator = allocator;
}
/**
* Params:
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
* init = The value to fill the new part of the array with if it becomes
* larger.
*
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter
*/
bool resizeArray(T)(shared Allocator allocator,
ref T[] array,
in size_t length,
T init = T.init)
{
void[] buf = array;
immutable oldLength = array.length;
if (!allocator.reallocate(buf, length * T.sizeof))
{
return false;
}
array = cast(T[]) buf;
if (oldLength < length)
{
array[oldLength .. $].uninitializedFill(init);
}
return true;
}
///
unittest
{
int[] p;
defaultAllocator.resizeArray(p, 20);
assert(p.length == 20);
defaultAllocator.resizeArray(p, 30);
assert(p.length == 30);
defaultAllocator.resizeArray(p, 10);
assert(p.length == 10);
defaultAllocator.resizeArray(p, 0);
assert(p is null);
}

View File

@ -37,13 +37,15 @@ struct RefCounted(T)
private T* payload;
}
private uint *counter;
private uint counter;
invariant
{
assert(counter is null || allocator !is null);
assert(counter == 0 || allocator !is null);
}
private shared Allocator allocator;
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
*
@ -54,20 +56,29 @@ struct RefCounted(T)
* Precondition: $(D_INLINECODE allocator !is null)
*/
this(T value, IAllocator allocator = theAllocator)
this(T value, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
initialize();
move(value, get);
this(allocator);
static if (!isReference!T)
{
payload = cast(T*) allocator.allocate(stateSize!T).ptr;
move(value, *payload);
counter = 1;
}
else if (value !is null)
{
move(value, payload);
counter = 1;
}
}
/// Ditto.
this(IAllocator allocator)
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
@ -77,28 +88,6 @@ struct RefCounted(T)
this.allocator = allocator;
}
/**
* Allocates the internal storage.
*/
private void initialize()
{
static if (isReference!T)
{
counter = allocator.make!uint(1);
}
else
{
// Allocate for the counter and the payload together.
auto p = allocator.allocate(uint.sizeof + T.sizeof);
if (p is null)
{
onOutOfMemoryError();
}
counter = emplace(cast(uint*) p.ptr, 1);
payload = cast(T*) p[uint.sizeof .. $].ptr;
}
}
/**
* Increases the reference counter by one.
*/
@ -106,7 +95,7 @@ struct RefCounted(T)
{
if (isInitialized)
{
++(*counter);
++counter;
}
}
@ -117,15 +106,14 @@ struct RefCounted(T)
*/
~this()
{
if (!isInitialized || (--(*counter)))
if (isInitialized && !--counter)
{
return;
static if (isReference!T)
{
allocator.dispose(payload);
payload = null;
}
}
allocator.dispose(payload);
payload = null;
allocator.dispose(counter);
counter = null;
}
/**
@ -135,7 +123,7 @@ struct RefCounted(T)
* If it is the last reference of the previously owned object,
* it will be destroyed.
*
* If the allocator wasn't set before, $(D_PSYMBOL theAllocator) will
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted).
*
@ -144,21 +132,18 @@ struct RefCounted(T)
*/
ref T opAssign(T rhs)
{
checkAllocator();
if (isInitialized)
if (allocator is null)
{
static if (isReference!T)
{
if (!--(*counter))
{
allocator.dispose(payload);
*counter = 1;
}
}
allocator = defaultAllocator;
}
else
static if (isReference!T)
{
initialize();
counter == 1 ? allocator.dispose(payload) : --counter;
}
else if (!isInitialized)
{
payload = cast(T*) allocator.allocate(stateSize!T).ptr;
counter = 1;
}
move(rhs, get);
return get;
@ -212,7 +197,7 @@ struct RefCounted(T)
*/
@property uint count() const pure nothrow @safe @nogc
{
return counter is null ? 0 : *counter;
return counter;
}
/**
@ -220,11 +205,9 @@ struct RefCounted(T)
*/
@property bool isInitialized() const pure nothrow @safe @nogc
{
return counter !is null;
return counter != 0;
}
mixin StructAllocator;
alias get this;
}
@ -247,9 +230,11 @@ version (unittest)
struct B
{
int prop;
@disable this();
this(int param1)
{
prop = param1;
}
}
}
@ -269,7 +254,7 @@ unittest
}
}
auto arr = theAllocator.makeArray!ubyte(2);
auto arr = defaultAllocator.makeArray!ubyte(2);
{
auto a = S(arr);
assert(a.member.count == 1);
@ -288,7 +273,7 @@ unittest
private unittest
{
uint destroyed;
auto a = theAllocator.make!A(destroyed);
auto a = defaultAllocator.make!A(destroyed);
assert(destroyed == 0);
{
@ -323,6 +308,7 @@ private unittest
static assert(!is(typeof(cast(int) (RefCounted!A()))));
static assert(is(RefCounted!B));
static assert(is(RefCounted!A));
}
/**
@ -341,40 +327,26 @@ private unittest
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*/
RefCounted!T refCounted(T, A...)(IAllocator allocator, auto ref A args)
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T)
{
auto rc = typeof(return)(allocator);
immutable toAllocate = max(stateSize!T, 1) + uint.sizeof;
auto p = allocator.allocate(toAllocate);
if (p is null)
static if (isReference!T)
{
onOutOfMemoryError();
}
scope (failure)
{
allocator.deallocate(p);
}
rc.counter = emplace(cast(uint*) p.ptr, 1);
static if (is(T == class))
{
rc.payload = emplace!T(p[uint.sizeof .. $], args);
return typeof(return)(allocator.make!T(args), allocator);
}
else
{
rc.payload = emplace(cast(T*) p[uint.sizeof .. $].ptr, args);
auto rc = typeof(return)(allocator);
rc.counter = 1;
rc.payload = allocator.make!T(args);
return rc;
}
return rc;
}
///
unittest
{
auto rc = theAllocator.refCounted!int(5);
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int param)
@ -395,7 +367,21 @@ unittest
private unittest
{
static assert(!is(theAllocator.refCounted!A));
static assert(!is(typeof(theAllocator.refCounted!B())));
static assert(is(typeof(theAllocator.refCounted!B(5))));
struct E
{
}
static assert(is(typeof(defaultAllocator.refCounted!bool(false))));
static assert(is(typeof(defaultAllocator.refCounted!B(5))));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get.prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.isInitialized);
}
}