mirror of
https://github.com/autc04/Retro68.git
synced 2024-12-02 18:53:22 +00:00
2713 lines
77 KiB
D
2713 lines
77 KiB
D
/**
|
|
* This module contains all functions related to an object's lifetime:
|
|
* allocation, resizing, deallocation, and finalization.
|
|
*
|
|
* Copyright: Copyright Digital Mars 2000 - 2012.
|
|
* License: Distributed under the
|
|
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
|
|
* (See accompanying file LICENSE)
|
|
* Authors: Walter Bright, Sean Kelly, Steven Schveighoffer
|
|
* Source: $(DRUNTIMESRC src/rt/_lifetime.d)
|
|
*/
|
|
|
|
module rt.lifetime;
|
|
|
|
import core.stdc.stdlib;
|
|
import core.stdc.string;
|
|
import core.stdc.stdarg;
|
|
import core.bitop;
|
|
import core.memory;
|
|
debug(PRINTF) import core.stdc.stdio;
|
|
static import rt.tlsgc;
|
|
|
|
alias BlkInfo = GC.BlkInfo;
|
|
alias BlkAttr = GC.BlkAttr;
|
|
import core.exception : onOutOfMemoryError, onFinalizeError, onInvalidMemoryOperationError;
|
|
|
|
private
|
|
{
|
|
alias bool function(Object) CollectHandler;
|
|
__gshared CollectHandler collectHandler = null;
|
|
|
|
extern (C) void _d_monitordelete(Object h, bool det);
|
|
|
|
enum : size_t
|
|
{
|
|
PAGESIZE = 4096,
|
|
BIGLENGTHMASK = ~(PAGESIZE - 1),
|
|
SMALLPAD = 1,
|
|
MEDPAD = ushort.sizeof,
|
|
LARGEPREFIX = 16, // 16 bytes padding at the front of the array
|
|
LARGEPAD = LARGEPREFIX + 1,
|
|
MAXSMALLSIZE = 256-SMALLPAD,
|
|
MAXMEDSIZE = (PAGESIZE / 2) - MEDPAD
|
|
}
|
|
}
|
|
|
|
private immutable bool callStructDtorsDuringGC;
|
|
|
|
extern (C) void lifetime_init()
|
|
{
|
|
// this is run before static ctors, so it is safe to modify immutables
|
|
import rt.config;
|
|
string s = rt_configOption("callStructDtorsDuringGC");
|
|
if (s != null)
|
|
cast() callStructDtorsDuringGC = s[0] == '1' || s[0] == 'y' || s[0] == 'Y';
|
|
else
|
|
cast() callStructDtorsDuringGC = true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void* _d_allocmemory(size_t sz)
|
|
{
|
|
return GC.malloc(sz);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) Object _d_newclass(const ClassInfo ci)
|
|
{
|
|
void* p;
|
|
|
|
debug(PRINTF) printf("_d_newclass(ci = %p, %s)\n", ci, cast(char *)ci.name);
|
|
if (ci.m_flags & TypeInfo_Class.ClassFlags.isCOMclass)
|
|
{ /* COM objects are not garbage collected, they are reference counted
|
|
* using AddRef() and Release(). They get free'd by C's free()
|
|
* function called by Release() when Release()'s reference count goes
|
|
* to zero.
|
|
*/
|
|
p = malloc(ci.initializer.length);
|
|
if (!p)
|
|
onOutOfMemoryError();
|
|
}
|
|
else
|
|
{
|
|
// TODO: should this be + 1 to avoid having pointers to the next block?
|
|
BlkAttr attr = BlkAttr.NONE;
|
|
// extern(C++) classes don't have a classinfo pointer in their vtable so the GC can't finalize them
|
|
if (ci.m_flags & TypeInfo_Class.ClassFlags.hasDtor
|
|
&& !(ci.m_flags & TypeInfo_Class.ClassFlags.isCPPclass))
|
|
attr |= BlkAttr.FINALIZE;
|
|
if (ci.m_flags & TypeInfo_Class.ClassFlags.noPointers)
|
|
attr |= BlkAttr.NO_SCAN;
|
|
p = GC.malloc(ci.initializer.length, attr, ci);
|
|
debug(PRINTF) printf(" p = %p\n", p);
|
|
}
|
|
|
|
debug(PRINTF)
|
|
{
|
|
printf("p = %p\n", p);
|
|
printf("ci = %p, ci.init.ptr = %p, len = %llu\n", ci, ci.initializer.ptr, cast(ulong)ci.initializer.length);
|
|
printf("vptr = %p\n", *cast(void**) ci.initializer);
|
|
printf("vtbl[0] = %p\n", (*cast(void***) ci.initializer)[0]);
|
|
printf("vtbl[1] = %p\n", (*cast(void***) ci.initializer)[1]);
|
|
printf("init[0] = %x\n", (cast(uint*) ci.initializer)[0]);
|
|
printf("init[1] = %x\n", (cast(uint*) ci.initializer)[1]);
|
|
printf("init[2] = %x\n", (cast(uint*) ci.initializer)[2]);
|
|
printf("init[3] = %x\n", (cast(uint*) ci.initializer)[3]);
|
|
printf("init[4] = %x\n", (cast(uint*) ci.initializer)[4]);
|
|
}
|
|
|
|
// initialize it
|
|
p[0 .. ci.initializer.length] = ci.initializer[];
|
|
|
|
debug(PRINTF) printf("initialization done\n");
|
|
return cast(Object) p;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_delinterface(void** p)
|
|
{
|
|
if (*p)
|
|
{
|
|
Interface* pi = **cast(Interface ***)*p;
|
|
Object o = cast(Object)(*p - pi.offset);
|
|
|
|
_d_delclass(&o);
|
|
*p = null;
|
|
}
|
|
}
|
|
|
|
|
|
// used for deletion
|
|
private extern (D) alias void function (Object) fp_t;
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_delclass(Object* p)
|
|
{
|
|
if (*p)
|
|
{
|
|
debug(PRINTF) printf("_d_delclass(%p)\n", *p);
|
|
|
|
ClassInfo **pc = cast(ClassInfo **)*p;
|
|
if (*pc)
|
|
{
|
|
ClassInfo c = **pc;
|
|
|
|
rt_finalize(cast(void*) *p);
|
|
|
|
if (c.deallocator)
|
|
{
|
|
fp_t fp = cast(fp_t)c.deallocator;
|
|
(*fp)(*p); // call deallocator
|
|
*p = null;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rt_finalize(cast(void*) *p);
|
|
}
|
|
GC.free(cast(void*) *p);
|
|
*p = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is called for a delete statement where the value
|
|
* being deleted is a pointer to a struct with a destructor
|
|
* but doesn't have an overloaded delete operator.
|
|
*/
|
|
extern (C) void _d_delstruct(void** p, TypeInfo_Struct inf)
|
|
{
|
|
if (*p)
|
|
{
|
|
debug(PRINTF) printf("_d_delstruct(%p, %p)\n", *p, cast(void*)inf);
|
|
|
|
inf.destroy(*p);
|
|
GC.free(*p);
|
|
*p = null;
|
|
}
|
|
}
|
|
|
|
// strip const/immutable/shared/inout from type info
|
|
inout(TypeInfo) unqualify(inout(TypeInfo) cti) pure nothrow @nogc
|
|
{
|
|
TypeInfo ti = cast() cti;
|
|
while (ti)
|
|
{
|
|
// avoid dynamic type casts
|
|
auto tti = typeid(ti);
|
|
if (tti is typeid(TypeInfo_Const))
|
|
ti = (cast(TypeInfo_Const)cast(void*)ti).base;
|
|
else if (tti is typeid(TypeInfo_Invariant))
|
|
ti = (cast(TypeInfo_Invariant)cast(void*)ti).base;
|
|
else if (tti is typeid(TypeInfo_Shared))
|
|
ti = (cast(TypeInfo_Shared)cast(void*)ti).base;
|
|
else if (tti is typeid(TypeInfo_Inout))
|
|
ti = (cast(TypeInfo_Inout)cast(void*)ti).base;
|
|
else
|
|
break;
|
|
}
|
|
return ti;
|
|
}
|
|
|
|
// size used to store the TypeInfo at the end of an allocation for structs that have a destructor
|
|
size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc
|
|
{
|
|
if (!callStructDtorsDuringGC)
|
|
return 0;
|
|
|
|
if (ti && typeid(ti) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast
|
|
{
|
|
auto sti = cast(TypeInfo_Struct)cast(void*)ti;
|
|
if (sti.xdtor)
|
|
return size_t.sizeof;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** dummy class used to lock for shared array appending */
|
|
private class ArrayAllocLengthLock
|
|
{}
|
|
|
|
|
|
/**
|
|
Set the allocated length of the array block. This is called
|
|
any time an array is appended to or its length is set.
|
|
|
|
The allocated block looks like this for blocks < PAGESIZE:
|
|
|
|
|elem0|elem1|elem2|...|elemN-1|emptyspace|N*elemsize|
|
|
|
|
|
|
The size of the allocated length at the end depends on the block size:
|
|
|
|
a block of 16 to 256 bytes has an 8-bit length.
|
|
|
|
a block with 512 to pagesize/2 bytes has a 16-bit length.
|
|
|
|
For blocks >= pagesize, the length is a size_t and is at the beginning of the
|
|
block. The reason we have to do this is because the block can extend into
|
|
more pages, so we cannot trust the block length if it sits at the end of the
|
|
block, because it might have just been extended. If we can prove in the
|
|
future that the block is unshared, we may be able to change this, but I'm not
|
|
sure it's important.
|
|
|
|
In order to do put the length at the front, we have to provide 16 bytes
|
|
buffer space in case the block has to be aligned properly. In x86, certain
|
|
SSE instructions will only work if the data is 16-byte aligned. In addition,
|
|
we need the sentinel byte to prevent accidental pointers to the next block.
|
|
Because of the extra overhead, we only do this for page size and above, where
|
|
the overhead is minimal compared to the block size.
|
|
|
|
So for those blocks, it looks like:
|
|
|
|
|N*elemsize|padding|elem0|elem1|...|elemN-1|emptyspace|sentinelbyte|
|
|
|
|
where elem0 starts 16 bytes after the first byte.
|
|
*/
|
|
bool __setArrayAllocLength(ref BlkInfo info, size_t newlength, bool isshared, const TypeInfo tinext, size_t oldlength = ~0) pure nothrow
|
|
{
|
|
import core.atomic;
|
|
|
|
size_t typeInfoSize = structTypeInfoSize(tinext);
|
|
|
|
if (info.size <= 256)
|
|
{
|
|
import core.checkedint;
|
|
|
|
bool overflow;
|
|
auto newlength_padded = addu(newlength,
|
|
addu(SMALLPAD, typeInfoSize, overflow),
|
|
overflow);
|
|
|
|
if (newlength_padded > info.size || overflow)
|
|
// new size does not fit inside block
|
|
return false;
|
|
|
|
auto length = cast(ubyte *)(info.base + info.size - typeInfoSize - SMALLPAD);
|
|
if (oldlength != ~0)
|
|
{
|
|
if (isshared)
|
|
{
|
|
return cas(cast(shared)length, cast(ubyte)oldlength, cast(ubyte)newlength);
|
|
}
|
|
else
|
|
{
|
|
if (*length == cast(ubyte)oldlength)
|
|
*length = cast(ubyte)newlength;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// setting the initial length, no cas needed
|
|
*length = cast(ubyte)newlength;
|
|
}
|
|
if (typeInfoSize)
|
|
{
|
|
auto typeInfo = cast(TypeInfo*)(info.base + info.size - size_t.sizeof);
|
|
*typeInfo = cast() tinext;
|
|
}
|
|
}
|
|
else if (info.size < PAGESIZE)
|
|
{
|
|
if (newlength + MEDPAD + typeInfoSize > info.size)
|
|
// new size does not fit inside block
|
|
return false;
|
|
auto length = cast(ushort *)(info.base + info.size - typeInfoSize - MEDPAD);
|
|
if (oldlength != ~0)
|
|
{
|
|
if (isshared)
|
|
{
|
|
return cas(cast(shared)length, cast(ushort)oldlength, cast(ushort)newlength);
|
|
}
|
|
else
|
|
{
|
|
if (*length == oldlength)
|
|
*length = cast(ushort)newlength;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// setting the initial length, no cas needed
|
|
*length = cast(ushort)newlength;
|
|
}
|
|
if (typeInfoSize)
|
|
{
|
|
auto typeInfo = cast(TypeInfo*)(info.base + info.size - size_t.sizeof);
|
|
*typeInfo = cast() tinext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (newlength + LARGEPAD > info.size)
|
|
// new size does not fit inside block
|
|
return false;
|
|
auto length = cast(size_t *)(info.base);
|
|
if (oldlength != ~0)
|
|
{
|
|
if (isshared)
|
|
{
|
|
return cas(cast(shared)length, cast(size_t)oldlength, cast(size_t)newlength);
|
|
}
|
|
else
|
|
{
|
|
if (*length == oldlength)
|
|
*length = newlength;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// setting the initial length, no cas needed
|
|
*length = newlength;
|
|
}
|
|
if (typeInfoSize)
|
|
{
|
|
auto typeInfo = cast(TypeInfo*)(info.base + size_t.sizeof);
|
|
*typeInfo = cast()tinext;
|
|
}
|
|
}
|
|
return true; // resize succeeded
|
|
}
|
|
|
|
/**
|
|
get the allocation size of the array for the given block (without padding or type info)
|
|
*/
|
|
size_t __arrayAllocLength(ref BlkInfo info, const TypeInfo tinext) pure nothrow
|
|
{
|
|
if (info.size <= 256)
|
|
return *cast(ubyte *)(info.base + info.size - structTypeInfoSize(tinext) - SMALLPAD);
|
|
|
|
if (info.size < PAGESIZE)
|
|
return *cast(ushort *)(info.base + info.size - structTypeInfoSize(tinext) - MEDPAD);
|
|
|
|
return *cast(size_t *)(info.base);
|
|
}
|
|
|
|
/**
|
|
get the start of the array for the given block
|
|
*/
|
|
void *__arrayStart(BlkInfo info) nothrow pure
|
|
{
|
|
return info.base + ((info.size & BIGLENGTHMASK) ? LARGEPREFIX : 0);
|
|
}
|
|
|
|
/**
|
|
get the padding required to allocate size bytes. Note that the padding is
|
|
NOT included in the passed in size. Therefore, do NOT call this function
|
|
with the size of an allocated block.
|
|
*/
|
|
size_t __arrayPad(size_t size, const TypeInfo tinext) nothrow pure @trusted
|
|
{
|
|
return size > MAXMEDSIZE ? LARGEPAD : ((size > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + structTypeInfoSize(tinext));
|
|
}
|
|
|
|
/**
|
|
allocate an array memory block by applying the proper padding and
|
|
assigning block attributes if not inherited from the existing block
|
|
*/
|
|
BlkInfo __arrayAlloc(size_t arrsize, const TypeInfo ti, const TypeInfo tinext) nothrow pure
|
|
{
|
|
import core.checkedint;
|
|
|
|
size_t typeInfoSize = structTypeInfoSize(tinext);
|
|
size_t padsize = arrsize > MAXMEDSIZE ? LARGEPAD : ((arrsize > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + typeInfoSize);
|
|
|
|
bool overflow;
|
|
auto padded_size = addu(arrsize, padsize, overflow);
|
|
|
|
if (overflow)
|
|
return BlkInfo();
|
|
|
|
uint attr = (!(tinext.flags & 1) ? BlkAttr.NO_SCAN : 0) | BlkAttr.APPENDABLE;
|
|
if (typeInfoSize)
|
|
attr |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE;
|
|
return GC.qalloc(padded_size, attr, ti);
|
|
}
|
|
|
|
BlkInfo __arrayAlloc(size_t arrsize, ref BlkInfo info, const TypeInfo ti, const TypeInfo tinext)
|
|
{
|
|
import core.checkedint;
|
|
|
|
if (!info.base)
|
|
return __arrayAlloc(arrsize, ti, tinext);
|
|
|
|
bool overflow;
|
|
auto padded_size = addu(arrsize, __arrayPad(arrsize, tinext), overflow);
|
|
if (overflow)
|
|
{
|
|
return BlkInfo();
|
|
}
|
|
|
|
return GC.qalloc(padded_size, info.attr, ti);
|
|
}
|
|
|
|
/**
|
|
cache for the lookup of the block info
|
|
*/
|
|
enum N_CACHE_BLOCKS=8;
|
|
|
|
// note this is TLS, so no need to sync.
|
|
BlkInfo *__blkcache_storage;
|
|
|
|
static if (N_CACHE_BLOCKS==1)
|
|
{
|
|
version=single_cache;
|
|
}
|
|
else
|
|
{
|
|
//version=simple_cache; // uncomment to test simple cache strategy
|
|
//version=random_cache; // uncomment to test random cache strategy
|
|
|
|
// ensure N_CACHE_BLOCKS is power of 2.
|
|
static assert(!((N_CACHE_BLOCKS - 1) & N_CACHE_BLOCKS));
|
|
|
|
version (random_cache)
|
|
{
|
|
int __nextRndNum = 0;
|
|
}
|
|
int __nextBlkIdx;
|
|
}
|
|
|
|
@property BlkInfo *__blkcache() nothrow
|
|
{
|
|
if (!__blkcache_storage)
|
|
{
|
|
// allocate the block cache for the first time
|
|
immutable size = BlkInfo.sizeof * N_CACHE_BLOCKS;
|
|
__blkcache_storage = cast(BlkInfo *)malloc(size);
|
|
memset(__blkcache_storage, 0, size);
|
|
}
|
|
return __blkcache_storage;
|
|
}
|
|
|
|
// called when thread is exiting.
|
|
static ~this()
|
|
{
|
|
// free the blkcache
|
|
if (__blkcache_storage)
|
|
{
|
|
free(__blkcache_storage);
|
|
__blkcache_storage = null;
|
|
}
|
|
}
|
|
|
|
|
|
// we expect this to be called with the lock in place
|
|
void processGCMarks(BlkInfo* cache, scope rt.tlsgc.IsMarkedDg isMarked) nothrow
|
|
{
|
|
// called after the mark routine to eliminate block cache data when it
|
|
// might be ready to sweep
|
|
|
|
debug(PRINTF) printf("processing GC Marks, %x\n", cache);
|
|
if (cache)
|
|
{
|
|
debug(PRINTF) foreach (i; 0 .. N_CACHE_BLOCKS)
|
|
{
|
|
printf("cache entry %d has base ptr %x\tsize %d\tflags %x\n", i, cache[i].base, cache[i].size, cache[i].attr);
|
|
}
|
|
auto cache_end = cache + N_CACHE_BLOCKS;
|
|
for (;cache < cache_end; ++cache)
|
|
{
|
|
if (cache.base != null && !isMarked(cache.base))
|
|
{
|
|
debug(PRINTF) printf("clearing cache entry at %x\n", cache.base);
|
|
cache.base = null; // clear that data.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// Bugzilla 10701 - segfault in GC
|
|
ubyte[] result; result.length = 4096;
|
|
GC.free(result.ptr);
|
|
GC.collect();
|
|
}
|
|
|
|
/**
|
|
Get the cached block info of an interior pointer. Returns null if the
|
|
interior pointer's block is not cached.
|
|
|
|
NOTE: The base ptr in this struct can be cleared asynchronously by the GC,
|
|
so any use of the returned BlkInfo should copy it and then check the
|
|
base ptr of the copy before actually using it.
|
|
|
|
TODO: Change this function so the caller doesn't have to be aware of this
|
|
issue. Either return by value and expect the caller to always check
|
|
the base ptr as an indication of whether the struct is valid, or set
|
|
the BlkInfo as a side-effect and return a bool to indicate success.
|
|
*/
|
|
BlkInfo *__getBlkInfo(void *interior) nothrow
|
|
{
|
|
BlkInfo *ptr = __blkcache;
|
|
version (single_cache)
|
|
{
|
|
if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size)
|
|
return ptr;
|
|
return null; // not in cache.
|
|
}
|
|
else version (simple_cache)
|
|
{
|
|
foreach (i; 0..N_CACHE_BLOCKS)
|
|
{
|
|
if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size)
|
|
return ptr;
|
|
ptr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// try to do a smart lookup, using __nextBlkIdx as the "head"
|
|
auto curi = ptr + __nextBlkIdx;
|
|
for (auto i = curi; i >= ptr; --i)
|
|
{
|
|
if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size)
|
|
return i;
|
|
}
|
|
|
|
for (auto i = ptr + N_CACHE_BLOCKS - 1; i > curi; --i)
|
|
{
|
|
if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size)
|
|
return i;
|
|
}
|
|
}
|
|
return null; // not in cache.
|
|
}
|
|
|
|
void __insertBlkInfoCache(BlkInfo bi, BlkInfo *curpos) nothrow
|
|
{
|
|
version (single_cache)
|
|
{
|
|
*__blkcache = bi;
|
|
}
|
|
else
|
|
{
|
|
version (simple_cache)
|
|
{
|
|
if (curpos)
|
|
*curpos = bi;
|
|
else
|
|
{
|
|
// note, this is a super-simple algorithm that does not care about
|
|
// most recently used. It simply uses a round-robin technique to
|
|
// cache block info. This means that the ordering of the cache
|
|
// doesn't mean anything. Certain patterns of allocation may
|
|
// render the cache near-useless.
|
|
__blkcache[__nextBlkIdx] = bi;
|
|
__nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1);
|
|
}
|
|
}
|
|
else version (random_cache)
|
|
{
|
|
// strategy: if the block currently is in the cache, move the
|
|
// current block index to the a random element and evict that
|
|
// element.
|
|
auto cache = __blkcache;
|
|
if (!curpos)
|
|
{
|
|
__nextBlkIdx = (__nextRndNum = 1664525 * __nextRndNum + 1013904223) & (N_CACHE_BLOCKS - 1);
|
|
curpos = cache + __nextBlkIdx;
|
|
}
|
|
else
|
|
{
|
|
__nextBlkIdx = curpos - cache;
|
|
}
|
|
*curpos = bi;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// strategy: If the block currently is in the cache, swap it with
|
|
// the head element. Otherwise, move the head element up by one,
|
|
// and insert it there.
|
|
//
|
|
auto cache = __blkcache;
|
|
if (!curpos)
|
|
{
|
|
__nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1);
|
|
curpos = cache + __nextBlkIdx;
|
|
}
|
|
else if (curpos !is cache + __nextBlkIdx)
|
|
{
|
|
*curpos = cache[__nextBlkIdx];
|
|
curpos = cache + __nextBlkIdx;
|
|
}
|
|
*curpos = bi;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shrink the "allocated" length of an array to be the exact size of the array.
|
|
* It doesn't matter what the current allocated length of the array is, the
|
|
* user is telling the runtime that he knows what he is doing.
|
|
*/
|
|
extern(C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) /+nothrow+/
|
|
{
|
|
// note, we do not care about shared. We are setting the length no matter
|
|
// what, so no lock is required.
|
|
debug(PRINTF) printf("_d_arrayshrinkfit, elemsize = %d, arr.ptr = x%x arr.length = %d\n", ti.next.tsize, arr.ptr, arr.length);
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize; // array element size
|
|
auto cursize = arr.length * size;
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
auto bic = isshared ? null : __getBlkInfo(arr.ptr);
|
|
auto info = bic ? *bic : GC.query(arr.ptr);
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
auto newsize = (arr.ptr - __arrayStart(info)) + cursize;
|
|
|
|
debug(PRINTF) printf("setting allocated size to %d\n", (arr.ptr - info.base) + cursize);
|
|
|
|
// destroy structs that become unused memory when array size is shrinked
|
|
if (typeid(tinext) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast
|
|
{
|
|
auto sti = cast(TypeInfo_Struct)cast(void*)tinext;
|
|
if (sti.xdtor)
|
|
{
|
|
auto oldsize = __arrayAllocLength(info, tinext);
|
|
if (oldsize > cursize)
|
|
finalize_array(arr.ptr + cursize, oldsize - cursize, sti);
|
|
}
|
|
}
|
|
// Note: Since we "assume" the append is safe, it means it is not shared.
|
|
// Since it is not shared, we also know it won't throw (no lock).
|
|
if (!__setArrayAllocLength(info, newsize, false, tinext))
|
|
onInvalidMemoryOperationError();
|
|
|
|
// cache the block if not already done.
|
|
if (!isshared && !bic)
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
|
|
package bool hasPostblit(in TypeInfo ti)
|
|
{
|
|
return (&ti.postblit).funcptr !is &TypeInfo.postblit;
|
|
}
|
|
|
|
void __doPostblit(void *ptr, size_t len, const TypeInfo ti)
|
|
{
|
|
if (!hasPostblit(ti))
|
|
return;
|
|
|
|
if (auto tis = cast(TypeInfo_Struct)ti)
|
|
{
|
|
// this is a struct, check the xpostblit member
|
|
auto pblit = tis.xpostblit;
|
|
if (!pblit)
|
|
// postblit not specified, no point in looping.
|
|
return;
|
|
|
|
// optimized for struct, call xpostblit directly for each element
|
|
immutable size = ti.tsize;
|
|
const eptr = ptr + len;
|
|
for (;ptr < eptr;ptr += size)
|
|
pblit(ptr);
|
|
}
|
|
else
|
|
{
|
|
// generic case, call the typeinfo's postblit function
|
|
immutable size = ti.tsize;
|
|
const eptr = ptr + len;
|
|
for (;ptr < eptr;ptr += size)
|
|
ti.postblit(ptr);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* set the array capacity. If the array capacity isn't currently large enough
|
|
* to hold the requested capacity (in number of elements), then the array is
|
|
* resized/reallocated to the appropriate size. Pass in a requested capacity
|
|
* of 0 to get the current capacity. Returns the number of elements that can
|
|
* actually be stored once the resizing is done.
|
|
*/
|
|
extern(C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* p)
|
|
in
|
|
{
|
|
assert(ti);
|
|
assert(!(*p).length || (*p).ptr);
|
|
}
|
|
body
|
|
{
|
|
// step 1, get the block
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
auto bic = isshared ? null : __getBlkInfo((*p).ptr);
|
|
auto info = bic ? *bic : GC.query((*p).ptr);
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize;
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
size_t reqsize = void;
|
|
|
|
asm
|
|
{
|
|
mov EAX, newcapacity;
|
|
mul EAX, size;
|
|
mov reqsize, EAX;
|
|
jnc Lcontinue;
|
|
}
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
size_t reqsize = void;
|
|
|
|
asm
|
|
{
|
|
mov RAX, newcapacity;
|
|
mul RAX, size;
|
|
mov reqsize, RAX;
|
|
jnc Lcontinue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import core.checkedint : mulu;
|
|
|
|
bool overflow = false;
|
|
size_t reqsize = mulu(size, newcapacity, overflow);
|
|
if (!overflow)
|
|
goto Lcontinue;
|
|
}
|
|
Loverflow:
|
|
onOutOfMemoryError();
|
|
assert(0);
|
|
Lcontinue:
|
|
|
|
// step 2, get the actual "allocated" size. If the allocated size does not
|
|
// match what we expect, then we will need to reallocate anyways.
|
|
|
|
// TODO: this probably isn't correct for shared arrays
|
|
size_t curallocsize = void;
|
|
size_t curcapacity = void;
|
|
size_t offset = void;
|
|
size_t arraypad = void;
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
if (info.size <= 256)
|
|
{
|
|
arraypad = SMALLPAD + structTypeInfoSize(tinext);
|
|
curallocsize = *(cast(ubyte *)(info.base + info.size - arraypad));
|
|
}
|
|
else if (info.size < PAGESIZE)
|
|
{
|
|
arraypad = MEDPAD + structTypeInfoSize(tinext);
|
|
curallocsize = *(cast(ushort *)(info.base + info.size - arraypad));
|
|
}
|
|
else
|
|
{
|
|
curallocsize = *(cast(size_t *)(info.base));
|
|
arraypad = LARGEPAD;
|
|
}
|
|
|
|
|
|
offset = (*p).ptr - __arrayStart(info);
|
|
if (offset + (*p).length * size != curallocsize)
|
|
{
|
|
curcapacity = 0;
|
|
}
|
|
else
|
|
{
|
|
// figure out the current capacity of the block from the point
|
|
// of view of the array.
|
|
curcapacity = info.size - offset - arraypad;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curallocsize = curcapacity = offset = 0;
|
|
}
|
|
debug(PRINTF) printf("_d_arraysetcapacity, p = x%d,%d, newcapacity=%d, info.size=%d, reqsize=%d, curallocsize=%d, curcapacity=%d, offset=%d\n", (*p).ptr, (*p).length, newcapacity, info.size, reqsize, curallocsize, curcapacity, offset);
|
|
|
|
if (curcapacity >= reqsize)
|
|
{
|
|
// no problems, the current allocated size is large enough.
|
|
return curcapacity / size;
|
|
}
|
|
|
|
// step 3, try to extend the array in place.
|
|
if (info.size >= PAGESIZE && curcapacity != 0)
|
|
{
|
|
auto extendsize = reqsize + offset + LARGEPAD - info.size;
|
|
auto u = GC.extend(info.base, extendsize, extendsize);
|
|
if (u)
|
|
{
|
|
// extend worked, save the new current allocated size
|
|
if (bic)
|
|
bic.size = u; // update cache
|
|
curcapacity = u - offset - LARGEPAD;
|
|
return curcapacity / size;
|
|
}
|
|
}
|
|
|
|
// step 4, if extending doesn't work, allocate a new array with at least the requested allocated size.
|
|
auto datasize = (*p).length * size;
|
|
// copy attributes from original block, or from the typeinfo if the
|
|
// original block doesn't exist.
|
|
info = __arrayAlloc(reqsize, info, ti, tinext);
|
|
if (info.base is null)
|
|
goto Loverflow;
|
|
// copy the data over.
|
|
// note that malloc will have initialized the data we did not request to 0.
|
|
auto tgt = __arrayStart(info);
|
|
memcpy(tgt, (*p).ptr, datasize);
|
|
|
|
// handle postblit
|
|
__doPostblit(tgt, datasize, tinext);
|
|
|
|
if (!(info.attr & BlkAttr.NO_SCAN))
|
|
{
|
|
// need to memset the newly requested data, except for the data that
|
|
// malloc returned that we didn't request.
|
|
void *endptr = tgt + reqsize;
|
|
void *begptr = tgt + datasize;
|
|
|
|
// sanity check
|
|
assert(endptr >= begptr);
|
|
memset(begptr, 0, endptr - begptr);
|
|
}
|
|
|
|
// set up the correct length
|
|
__setArrayAllocLength(info, datasize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
|
|
*p = (cast(void*)tgt)[0 .. (*p).length];
|
|
|
|
// determine the padding. This has to be done manually because __arrayPad
|
|
// assumes you are not counting the pad size, and info.size does include
|
|
// the pad.
|
|
if (info.size <= 256)
|
|
arraypad = SMALLPAD + structTypeInfoSize(tinext);
|
|
else if (info.size < PAGESIZE)
|
|
arraypad = MEDPAD + structTypeInfoSize(tinext);
|
|
else
|
|
arraypad = LARGEPAD;
|
|
|
|
curcapacity = info.size - arraypad;
|
|
return curcapacity / size;
|
|
}
|
|
|
|
/**
|
|
* Allocate a new uninitialized array of length elements.
|
|
* ti is the type of the resulting array, or pointer to element.
|
|
*/
|
|
extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow
|
|
{
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize;
|
|
|
|
debug(PRINTF) printf("_d_newarrayU(length = x%x, size = %d)\n", length, size);
|
|
if (length == 0 || size == 0)
|
|
return null;
|
|
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
mov EAX,size ;
|
|
mul EAX,length ;
|
|
mov size,EAX ;
|
|
jnc Lcontinue ;
|
|
}
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
mov RAX,size ;
|
|
mul RAX,length ;
|
|
mov size,RAX ;
|
|
jnc Lcontinue ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import core.checkedint : mulu;
|
|
|
|
bool overflow = false;
|
|
size = mulu(size, length, overflow);
|
|
if (!overflow)
|
|
goto Lcontinue;
|
|
}
|
|
Loverflow:
|
|
onOutOfMemoryError();
|
|
assert(0);
|
|
Lcontinue:
|
|
|
|
auto info = __arrayAlloc(size, ti, tinext);
|
|
if (!info.base)
|
|
goto Loverflow;
|
|
debug(PRINTF) printf(" p = %p\n", info.base);
|
|
// update the length of the array
|
|
auto arrstart = __arrayStart(info);
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
__setArrayAllocLength(info, size, isshared, tinext);
|
|
return arrstart[0..length];
|
|
}
|
|
|
|
/**
|
|
* Allocate a new array of length elements.
|
|
* ti is the type of the resulting array, or pointer to element.
|
|
* (For when the array is initialized to 0)
|
|
*/
|
|
extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) pure nothrow
|
|
{
|
|
void[] result = _d_newarrayU(ti, length);
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize;
|
|
|
|
memset(result.ptr, 0, size * length);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* For when the array has a non-zero initializer.
|
|
*/
|
|
extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) pure nothrow
|
|
{
|
|
import core.internal.traits : TypeTuple;
|
|
|
|
void[] result = _d_newarrayU(ti, length);
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize;
|
|
|
|
auto init = tinext.initializer();
|
|
|
|
switch (init.length)
|
|
{
|
|
foreach (T; TypeTuple!(ubyte, ushort, uint, ulong))
|
|
{
|
|
case T.sizeof:
|
|
(cast(T*)result.ptr)[0 .. size * length / T.sizeof] = *cast(T*)init.ptr;
|
|
return result;
|
|
}
|
|
|
|
default:
|
|
{
|
|
immutable sz = init.length;
|
|
for (size_t u = 0; u < size * length; u += sz)
|
|
memcpy(result.ptr + u, init.ptr, sz);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void[] _d_newarrayOpT(alias op)(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
debug(PRINTF) printf("_d_newarrayOpT(ndims = %d)\n", dims.length);
|
|
if (dims.length == 0)
|
|
return null;
|
|
|
|
void[] foo(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
auto tinext = unqualify(ti.next);
|
|
auto dim = dims[0];
|
|
|
|
debug(PRINTF) printf("foo(ti = %p, ti.next = %p, dim = %d, ndims = %d\n", ti, ti.next, dim, dims.length);
|
|
if (dims.length == 1)
|
|
{
|
|
auto r = op(ti, dim);
|
|
return *cast(void[]*)(&r);
|
|
}
|
|
|
|
auto allocsize = (void[]).sizeof * dim;
|
|
auto info = __arrayAlloc(allocsize, ti, tinext);
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
__setArrayAllocLength(info, allocsize, isshared, tinext);
|
|
auto p = __arrayStart(info)[0 .. dim];
|
|
|
|
foreach (i; 0..dim)
|
|
{
|
|
(cast(void[]*)p.ptr)[i] = foo(tinext, dims[1..$]);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
auto result = foo(ti, dims);
|
|
debug(PRINTF) printf("result = %llx\n", result.ptr);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
debug(PRINTF) printf("_d_newarraymT(dims.length = %d)\n", dims.length);
|
|
|
|
if (dims.length == 0)
|
|
return null;
|
|
else
|
|
{
|
|
return _d_newarrayOpT!(_d_newarrayT)(ti, dims);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
debug(PRINTF) printf("_d_newarraymiT(dims.length = %d)\n", dims.length);
|
|
|
|
if (dims.length == 0)
|
|
return null;
|
|
else
|
|
{
|
|
return _d_newarrayOpT!(_d_newarrayiT)(ti, dims);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocate an uninitialized non-array item.
|
|
* This is an optimization to avoid things needed for arrays like the __arrayPad(size).
|
|
*/
|
|
extern (C) void* _d_newitemU(in TypeInfo _ti)
|
|
{
|
|
auto ti = unqualify(_ti);
|
|
auto flags = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0;
|
|
immutable tiSize = structTypeInfoSize(ti);
|
|
immutable size = ti.tsize + tiSize;
|
|
if (tiSize)
|
|
flags |= BlkAttr.STRUCTFINAL | BlkAttr.FINALIZE;
|
|
|
|
auto blkInf = GC.qalloc(size, flags, ti);
|
|
auto p = blkInf.base;
|
|
|
|
if (tiSize)
|
|
*cast(TypeInfo*)(p + blkInf.size - tiSize) = cast() ti;
|
|
|
|
return p;
|
|
}
|
|
|
|
/// Same as above, zero initializes the item.
|
|
extern (C) void* _d_newitemT(in TypeInfo _ti)
|
|
{
|
|
auto p = _d_newitemU(_ti);
|
|
memset(p, 0, _ti.tsize);
|
|
return p;
|
|
}
|
|
|
|
/// Same as above, for item with non-zero initializer.
|
|
extern (C) void* _d_newitemiT(in TypeInfo _ti)
|
|
{
|
|
auto p = _d_newitemU(_ti);
|
|
auto init = _ti.initializer();
|
|
assert(init.length <= _ti.tsize);
|
|
memcpy(p, init.ptr, init.length);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
struct Array
|
|
{
|
|
size_t length;
|
|
byte* data;
|
|
}
|
|
|
|
|
|
/**
|
|
* This function has been replaced by _d_delarray_t
|
|
*/
|
|
extern (C) void _d_delarray(void[]* p)
|
|
{
|
|
_d_delarray_t(p, null);
|
|
}
|
|
|
|
debug(PRINTF)
|
|
{
|
|
extern(C) void printArrayCache()
|
|
{
|
|
auto ptr = __blkcache;
|
|
printf("CACHE: \n");
|
|
foreach (i; 0 .. N_CACHE_BLOCKS)
|
|
{
|
|
printf(" %d\taddr:% .8x\tsize:% .10d\tflags:% .8x\n", i, ptr[i].base, ptr[i].size, ptr[i].attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_delarray_t(void[]* p, const TypeInfo_Struct ti)
|
|
{
|
|
if (p)
|
|
{
|
|
auto bic = __getBlkInfo(p.ptr);
|
|
auto info = bic ? *bic : GC.query(p.ptr);
|
|
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
if (ti) // ti non-null only if ti is a struct with dtor
|
|
{
|
|
void* start = __arrayStart(info);
|
|
size_t length = __arrayAllocLength(info, ti);
|
|
finalize_array(start, length, ti);
|
|
}
|
|
|
|
// if p is in the cache, clear it there as well
|
|
if (bic)
|
|
bic.base = null;
|
|
|
|
GC.free(info.base);
|
|
*p = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
__gshared size_t countDtor = 0;
|
|
struct S
|
|
{
|
|
int x;
|
|
~this() { countDtor++; }
|
|
}
|
|
// destroy large array with x.ptr not base address of allocation
|
|
auto x = new S[10000];
|
|
void* p = x.ptr;
|
|
assert(GC.addrOf(p) != null);
|
|
delete x;
|
|
assert(GC.addrOf(p) == null);
|
|
assert(countDtor == 10000);
|
|
|
|
// destroy full array even if only slice passed
|
|
auto y = new S[400];
|
|
auto z = y[200 .. 300];
|
|
p = z.ptr;
|
|
assert(GC.addrOf(p) != null);
|
|
delete z;
|
|
assert(GC.addrOf(p) == null);
|
|
assert(countDtor == 10000 + 400);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_delmemory(void* *p)
|
|
{
|
|
if (*p)
|
|
{
|
|
GC.free(*p);
|
|
*p = null;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_callinterfacefinalizer(void *p)
|
|
{
|
|
if (p)
|
|
{
|
|
Interface *pi = **cast(Interface ***)p;
|
|
Object o = cast(Object)(p - pi.offset);
|
|
rt_finalize(cast(void*)o);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void _d_callfinalizer(void* p)
|
|
{
|
|
rt_finalize( p );
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void rt_setCollectHandler(CollectHandler h)
|
|
{
|
|
collectHandler = h;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) CollectHandler rt_getCollectHandler()
|
|
{
|
|
return collectHandler;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) int rt_hasFinalizerInSegment(void* p, size_t size, uint attr, in void[] segment) nothrow
|
|
{
|
|
if (attr & BlkAttr.STRUCTFINAL)
|
|
{
|
|
if (attr & BlkAttr.APPENDABLE)
|
|
return hasArrayFinalizerInSegment(p, size, segment);
|
|
return hasStructFinalizerInSegment(p, size, segment);
|
|
}
|
|
|
|
// otherwise class
|
|
auto ppv = cast(void**) p;
|
|
if (!p || !*ppv)
|
|
return false;
|
|
|
|
auto c = *cast(ClassInfo*)*ppv;
|
|
do
|
|
{
|
|
auto pf = c.destructor;
|
|
if (cast(size_t)(pf - segment.ptr) < segment.length) return true;
|
|
}
|
|
while ((c = c.base) !is null);
|
|
|
|
return false;
|
|
}
|
|
|
|
int hasStructFinalizerInSegment(void* p, size_t size, in void[] segment) nothrow
|
|
{
|
|
if (!p)
|
|
return false;
|
|
|
|
auto ti = *cast(TypeInfo_Struct*)(p + size - size_t.sizeof);
|
|
return cast(size_t)(cast(void*)ti.xdtor - segment.ptr) < segment.length;
|
|
}
|
|
|
|
int hasArrayFinalizerInSegment(void* p, size_t size, in void[] segment) nothrow
|
|
{
|
|
if (!p)
|
|
return false;
|
|
|
|
TypeInfo_Struct si = void;
|
|
if (size < PAGESIZE)
|
|
si = *cast(TypeInfo_Struct*)(p + size - size_t.sizeof);
|
|
else
|
|
si = *cast(TypeInfo_Struct*)(p + size_t.sizeof);
|
|
|
|
return cast(size_t)(cast(void*)si.xdtor - segment.ptr) < segment.length;
|
|
}
|
|
|
|
// called by the GC
|
|
void finalize_array2(void* p, size_t size) nothrow
|
|
{
|
|
debug(PRINTF) printf("rt_finalize_array2(p = %p)\n", p);
|
|
|
|
TypeInfo_Struct si = void;
|
|
if (size <= 256)
|
|
{
|
|
si = *cast(TypeInfo_Struct*)(p + size - size_t.sizeof);
|
|
size = *cast(ubyte*)(p + size - size_t.sizeof - SMALLPAD);
|
|
}
|
|
else if (size < PAGESIZE)
|
|
{
|
|
si = *cast(TypeInfo_Struct*)(p + size - size_t.sizeof);
|
|
size = *cast(ushort*)(p + size - size_t.sizeof - MEDPAD);
|
|
}
|
|
else
|
|
{
|
|
si = *cast(TypeInfo_Struct*)(p + size_t.sizeof);
|
|
size = *cast(size_t*)p;
|
|
p += LARGEPREFIX;
|
|
}
|
|
|
|
try
|
|
{
|
|
finalize_array(p, size, si);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
onFinalizeError(si, e);
|
|
}
|
|
}
|
|
|
|
void finalize_array(void* p, size_t size, const TypeInfo_Struct si)
|
|
{
|
|
// Due to the fact that the delete operator calls destructors
|
|
// for arrays from the last element to the first, we maintain
|
|
// compatibility here by doing the same.
|
|
auto tsize = si.tsize;
|
|
for (auto curP = p + size - tsize; curP >= p; curP -= tsize)
|
|
{
|
|
// call destructor
|
|
si.destroy(curP);
|
|
}
|
|
}
|
|
|
|
// called by the GC
|
|
void finalize_struct(void* p, size_t size) nothrow
|
|
{
|
|
debug(PRINTF) printf("finalize_struct(p = %p)\n", p);
|
|
|
|
auto ti = *cast(TypeInfo_Struct*)(p + size - size_t.sizeof);
|
|
try
|
|
{
|
|
ti.destroy(p); // call destructor
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
onFinalizeError(ti, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true) nothrow
|
|
{
|
|
debug(PRINTF) printf("rt_finalize2(p = %p)\n", p);
|
|
|
|
auto ppv = cast(void**) p;
|
|
if (!p || !*ppv)
|
|
return;
|
|
|
|
auto pc = cast(ClassInfo*) *ppv;
|
|
try
|
|
{
|
|
if (det || collectHandler is null || collectHandler(cast(Object) p))
|
|
{
|
|
auto c = *pc;
|
|
do
|
|
{
|
|
if (c.destructor)
|
|
(cast(fp_t) c.destructor)(cast(Object) p); // call destructor
|
|
}
|
|
while ((c = c.base) !is null);
|
|
}
|
|
|
|
if (ppv[1]) // if monitor is not null
|
|
_d_monitordelete(cast(Object) p, det);
|
|
|
|
if (resetMemory)
|
|
{
|
|
auto w = (*pc).initializer;
|
|
p[0 .. w.length] = w[];
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
onFinalizeError(*pc, e);
|
|
}
|
|
finally
|
|
{
|
|
*ppv = null; // zero vptr even if `resetMemory` is false
|
|
}
|
|
}
|
|
|
|
extern (C) void rt_finalize(void* p, bool det = true)
|
|
{
|
|
rt_finalize2(p, det, true);
|
|
}
|
|
|
|
extern (C) void rt_finalizeFromGC(void* p, size_t size, uint attr)
|
|
{
|
|
// to verify: reset memory necessary?
|
|
if (!(attr & BlkAttr.STRUCTFINAL))
|
|
rt_finalize2(p, false, false); // class
|
|
else if (attr & BlkAttr.APPENDABLE)
|
|
finalize_array2(p, size); // array of structs
|
|
else
|
|
finalize_struct(p, size); // struct
|
|
}
|
|
|
|
|
|
/**
|
|
* Resize dynamic arrays with 0 initializers.
|
|
*/
|
|
extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p)
|
|
in
|
|
{
|
|
assert(ti);
|
|
assert(!(*p).length || (*p).ptr);
|
|
}
|
|
body
|
|
{
|
|
debug(PRINTF)
|
|
{
|
|
//printf("_d_arraysetlengthT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength);
|
|
if (p)
|
|
printf("\tp.ptr = %p, p.length = %d\n", (*p).ptr, (*p).length);
|
|
}
|
|
|
|
void* newdata = void;
|
|
if (newlength)
|
|
{
|
|
if (newlength <= (*p).length)
|
|
{
|
|
*p = (*p)[0 .. newlength];
|
|
newdata = (*p).ptr;
|
|
return newdata[0 .. newlength];
|
|
}
|
|
auto tinext = unqualify(ti.next);
|
|
size_t sizeelem = tinext.tsize;
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
size_t newsize = void;
|
|
|
|
asm pure nothrow @nogc
|
|
{
|
|
mov EAX, newlength;
|
|
mul EAX, sizeelem;
|
|
mov newsize, EAX;
|
|
jc Loverflow;
|
|
}
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
size_t newsize = void;
|
|
|
|
asm pure nothrow @nogc
|
|
{
|
|
mov RAX, newlength;
|
|
mul RAX, sizeelem;
|
|
mov newsize, RAX;
|
|
jc Loverflow;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import core.checkedint : mulu;
|
|
|
|
bool overflow = false;
|
|
size_t newsize = mulu(sizeelem, newlength, overflow);
|
|
if (overflow)
|
|
goto Loverflow;
|
|
}
|
|
|
|
debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength);
|
|
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
|
|
if ((*p).ptr)
|
|
{
|
|
newdata = (*p).ptr;
|
|
if (newlength > (*p).length)
|
|
{
|
|
size_t size = (*p).length * sizeelem;
|
|
auto bic = isshared ? null : __getBlkInfo((*p).ptr);
|
|
auto info = bic ? *bic : GC.query((*p).ptr);
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
// calculate the extent of the array given the base.
|
|
size_t offset = (*p).ptr - __arrayStart(info);
|
|
if (info.size >= PAGESIZE)
|
|
{
|
|
// size of array is at the front of the block
|
|
if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// check to see if it failed because there is not
|
|
// enough space
|
|
if (*(cast(size_t*)info.base) == size + offset)
|
|
{
|
|
// not enough space, try extending
|
|
auto extendsize = newsize + offset + LARGEPAD - info.size;
|
|
auto u = GC.extend(info.base, extendsize, extendsize);
|
|
if (u)
|
|
{
|
|
// extend worked, now try setting the length
|
|
// again.
|
|
info.size = u;
|
|
if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
goto L1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't do it, reallocate
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
// add this to the cache, it wasn't present previously.
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// could not resize in place
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
// add this to the cache, it wasn't present previously.
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (info.base)
|
|
{
|
|
L2:
|
|
if (bic)
|
|
{
|
|
// a chance that flags have changed since this was cached, we should fetch the most recent flags
|
|
info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE;
|
|
}
|
|
info = __arrayAlloc(newsize, info, ti, tinext);
|
|
}
|
|
else
|
|
{
|
|
info = __arrayAlloc(newsize, ti, tinext);
|
|
}
|
|
|
|
if (info.base is null)
|
|
goto Loverflow;
|
|
|
|
__setArrayAllocLength(info, newsize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
newdata = cast(byte *)__arrayStart(info);
|
|
newdata[0 .. size] = (*p).ptr[0 .. size];
|
|
|
|
// do postblit processing
|
|
__doPostblit(newdata, size, tinext);
|
|
}
|
|
L1:
|
|
memset(newdata + size, 0, newsize - size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pointer was null, need to allocate
|
|
auto info = __arrayAlloc(newsize, ti, tinext);
|
|
if (info.base is null)
|
|
goto Loverflow;
|
|
__setArrayAllocLength(info, newsize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, null);
|
|
newdata = cast(byte *)__arrayStart(info);
|
|
memset(newdata, 0, newsize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newdata = (*p).ptr;
|
|
}
|
|
|
|
*p = newdata[0 .. newlength];
|
|
return *p;
|
|
|
|
Loverflow:
|
|
onOutOfMemoryError();
|
|
assert(0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Resize arrays for non-zero initializers.
|
|
* p pointer to array lvalue to be updated
|
|
* newlength new .length property of array
|
|
* sizeelem size of each element of array
|
|
* initsize size of initializer
|
|
* ... initializer
|
|
*/
|
|
extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p)
|
|
in
|
|
{
|
|
assert(!(*p).length || (*p).ptr);
|
|
}
|
|
body
|
|
{
|
|
void* newdata;
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize;
|
|
auto initializer = tinext.initializer();
|
|
auto initsize = initializer.length;
|
|
|
|
assert(sizeelem);
|
|
assert(initsize);
|
|
assert(initsize <= sizeelem);
|
|
assert((sizeelem / initsize) * initsize == sizeelem);
|
|
|
|
debug(PRINTF)
|
|
{
|
|
printf("_d_arraysetlengthiT(p = %p, sizeelem = %d, newlength = %d, initsize = %d)\n", p, sizeelem, newlength, initsize);
|
|
if (p)
|
|
printf("\tp.data = %p, p.length = %d\n", (*p).ptr, (*p).length);
|
|
}
|
|
|
|
if (newlength)
|
|
{
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
size_t newsize = void;
|
|
|
|
asm
|
|
{
|
|
mov EAX,newlength ;
|
|
mul EAX,sizeelem ;
|
|
mov newsize,EAX ;
|
|
jc Loverflow ;
|
|
}
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
size_t newsize = void;
|
|
|
|
asm
|
|
{
|
|
mov RAX,newlength ;
|
|
mul RAX,sizeelem ;
|
|
mov newsize,RAX ;
|
|
jc Loverflow ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
import core.checkedint : mulu;
|
|
|
|
bool overflow = false;
|
|
size_t newsize = mulu(sizeelem, newlength, overflow);
|
|
if (overflow)
|
|
goto Loverflow;
|
|
}
|
|
debug(PRINTF) printf("newsize = %x, newlength = %x\n", newsize, newlength);
|
|
|
|
|
|
size_t size = (*p).length * sizeelem;
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
if ((*p).ptr)
|
|
{
|
|
newdata = (*p).ptr;
|
|
if (newlength > (*p).length)
|
|
{
|
|
auto bic = isshared ? null : __getBlkInfo((*p).ptr);
|
|
auto info = bic ? *bic : GC.query((*p).ptr);
|
|
|
|
// calculate the extent of the array given the base.
|
|
size_t offset = (*p).ptr - __arrayStart(info);
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
if (info.size >= PAGESIZE)
|
|
{
|
|
// size of array is at the front of the block
|
|
if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// check to see if it failed because there is not
|
|
// enough space
|
|
if (*(cast(size_t*)info.base) == size + offset)
|
|
{
|
|
// not enough space, try extending
|
|
auto extendsize = newsize + offset + LARGEPAD - info.size;
|
|
auto u = GC.extend(info.base, extendsize, extendsize);
|
|
if (u)
|
|
{
|
|
// extend worked, now try setting the length
|
|
// again.
|
|
info.size = u;
|
|
if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
goto L1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't do it, reallocate
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
// add this to the cache, it wasn't present previously.
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// could not resize in place
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
// add this to the cache, it wasn't present previously.
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not appendable or not part of the heap yet.
|
|
if (info.base)
|
|
{
|
|
L2:
|
|
if (bic)
|
|
{
|
|
// a chance that flags have changed since this was cached, we should fetch the most recent flags
|
|
info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE;
|
|
}
|
|
info = __arrayAlloc(newsize, info, ti, tinext);
|
|
}
|
|
else
|
|
{
|
|
info = __arrayAlloc(newsize, ti, tinext);
|
|
}
|
|
__setArrayAllocLength(info, newsize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
newdata = cast(byte *)__arrayStart(info);
|
|
newdata[0 .. size] = (*p).ptr[0 .. size];
|
|
|
|
// do postblit processing
|
|
__doPostblit(newdata, size, tinext);
|
|
}
|
|
L1: ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// length was zero, need to allocate
|
|
auto info = __arrayAlloc(newsize, ti, tinext);
|
|
__setArrayAllocLength(info, newsize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, null);
|
|
newdata = cast(byte *)__arrayStart(info);
|
|
}
|
|
|
|
auto q = initializer.ptr; // pointer to initializer
|
|
|
|
if (newsize > size)
|
|
{
|
|
if (initsize == 1)
|
|
{
|
|
debug(PRINTF) printf("newdata = %p, size = %d, newsize = %d, *q = %d\n", newdata, size, newsize, *cast(byte*)q);
|
|
memset(newdata + size, *cast(byte*)q, newsize - size);
|
|
}
|
|
else
|
|
{
|
|
for (size_t u = size; u < newsize; u += initsize)
|
|
{
|
|
memcpy(newdata + u, q, initsize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newdata = (*p).ptr;
|
|
}
|
|
|
|
*p = newdata[0 .. newlength];
|
|
return *p;
|
|
|
|
Loverflow:
|
|
onOutOfMemoryError();
|
|
assert(0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Append y[] to array x[]
|
|
*/
|
|
extern (C) void[] _d_arrayappendT(const TypeInfo ti, ref byte[] x, byte[] y)
|
|
{
|
|
auto length = x.length;
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize; // array element size
|
|
_d_arrayappendcTX(ti, x, y.length);
|
|
memcpy(x.ptr + length * sizeelem, y.ptr, y.length * sizeelem);
|
|
|
|
// do postblit
|
|
__doPostblit(x.ptr + length * sizeelem, y.length * sizeelem, tinext);
|
|
return x;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
size_t newCapacity(size_t newlength, size_t size)
|
|
{
|
|
version (none)
|
|
{
|
|
size_t newcap = newlength * size;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Better version by Dave Fladebo:
|
|
* This uses an inverse logorithmic algorithm to pre-allocate a bit more
|
|
* space for larger arrays.
|
|
* - Arrays smaller than PAGESIZE bytes are left as-is, so for the most
|
|
* common cases, memory allocation is 1 to 1. The small overhead added
|
|
* doesn't affect small array perf. (it's virtually the same as
|
|
* current).
|
|
* - Larger arrays have some space pre-allocated.
|
|
* - As the arrays grow, the relative pre-allocated space shrinks.
|
|
* - The logorithmic algorithm allocates relatively more space for
|
|
* mid-size arrays, making it very fast for medium arrays (for
|
|
* mid-to-large arrays, this turns out to be quite a bit faster than the
|
|
* equivalent realloc() code in C, on Linux at least. Small arrays are
|
|
* just as fast as GCC).
|
|
* - Perhaps most importantly, overall memory usage and stress on the GC
|
|
* is decreased significantly for demanding environments.
|
|
*/
|
|
size_t newcap = newlength * size;
|
|
size_t newext = 0;
|
|
|
|
if (newcap > PAGESIZE)
|
|
{
|
|
//double mult2 = 1.0 + (size / log10(pow(newcap * 2.0,2.0)));
|
|
|
|
// redo above line using only integer math
|
|
|
|
/*static int log2plus1(size_t c)
|
|
{ int i;
|
|
|
|
if (c == 0)
|
|
i = -1;
|
|
else
|
|
for (i = 1; c >>= 1; i++)
|
|
{
|
|
}
|
|
return i;
|
|
}*/
|
|
|
|
/* The following setting for mult sets how much bigger
|
|
* the new size will be over what is actually needed.
|
|
* 100 means the same size, more means proportionally more.
|
|
* More means faster but more memory consumption.
|
|
*/
|
|
//long mult = 100 + (1000L * size) / (6 * log2plus1(newcap));
|
|
//long mult = 100 + (1000L * size) / log2plus1(newcap);
|
|
long mult = 100 + (1000L) / (bsr(newcap) + 1);
|
|
|
|
// testing shows 1.02 for large arrays is about the point of diminishing return
|
|
//
|
|
// Commented out because the multipler will never be < 102. In order for it to be < 2,
|
|
// then 1000L / (bsr(x) + 1) must be > 2. The highest bsr(x) + 1
|
|
// could be is 65 (64th bit set), and 1000L / 64 is much larger
|
|
// than 2. We need 500 bit integers for 101 to be achieved :)
|
|
/*if (mult < 102)
|
|
mult = 102;*/
|
|
/*newext = cast(size_t)((newcap * mult) / 100);
|
|
newext -= newext % size;*/
|
|
// This version rounds up to the next element, and avoids using
|
|
// mod.
|
|
newext = cast(size_t)((newlength * mult + 99) / 100) * size;
|
|
debug(PRINTF) printf("mult: %2.2f, alloc: %2.2f\n",mult/100.0,newext / cast(double)size);
|
|
}
|
|
newcap = newext > newcap ? newext : newcap;
|
|
debug(PRINTF) printf("newcap = %d, newlength = %d, size = %d\n", newcap, newlength, size);
|
|
}
|
|
return newcap;
|
|
}
|
|
|
|
|
|
/**************************************
|
|
* Extend an array by n elements.
|
|
* Caller must initialize those elements.
|
|
*/
|
|
extern (C)
|
|
byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n)
|
|
{
|
|
// This is a cut&paste job from _d_arrayappendT(). Should be refactored.
|
|
|
|
// only optimize array append where ti is not a shared type
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize; // array element size
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
auto bic = isshared ? null : __getBlkInfo(px.ptr);
|
|
auto info = bic ? *bic : GC.query(px.ptr);
|
|
auto length = px.length;
|
|
auto newlength = length + n;
|
|
auto newsize = newlength * sizeelem;
|
|
auto size = length * sizeelem;
|
|
size_t newcap = void; // for scratch space
|
|
|
|
// calculate the extent of the array given the base.
|
|
size_t offset = cast(void*)px.ptr - __arrayStart(info);
|
|
if (info.base && (info.attr & BlkAttr.APPENDABLE))
|
|
{
|
|
if (info.size >= PAGESIZE)
|
|
{
|
|
// size of array is at the front of the block
|
|
if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// check to see if it failed because there is not
|
|
// enough space
|
|
newcap = newCapacity(newlength, sizeelem);
|
|
if (*(cast(size_t*)info.base) == size + offset)
|
|
{
|
|
// not enough space, try extending
|
|
auto extendoffset = offset + LARGEPAD - info.size;
|
|
auto u = GC.extend(info.base, newsize + extendoffset, newcap + extendoffset);
|
|
if (u)
|
|
{
|
|
// extend worked, now try setting the length
|
|
// again.
|
|
info.size = u;
|
|
if (__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
goto L1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't do it, reallocate
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else if (!__setArrayAllocLength(info, newsize + offset, isshared, tinext, size + offset))
|
|
{
|
|
// could not resize in place
|
|
newcap = newCapacity(newlength, sizeelem);
|
|
goto L2;
|
|
}
|
|
else if (!isshared && !bic)
|
|
{
|
|
__insertBlkInfoCache(info, null);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not appendable or is null
|
|
newcap = newCapacity(newlength, sizeelem);
|
|
if (info.base)
|
|
{
|
|
L2:
|
|
if (bic)
|
|
{
|
|
// a chance that flags have changed since this was cached, we should fetch the most recent flags
|
|
info.attr = GC.getAttr(info.base) | BlkAttr.APPENDABLE;
|
|
}
|
|
info = __arrayAlloc(newcap, info, ti, tinext);
|
|
}
|
|
else
|
|
{
|
|
info = __arrayAlloc(newcap, ti, tinext);
|
|
}
|
|
__setArrayAllocLength(info, newsize, isshared, tinext);
|
|
if (!isshared)
|
|
__insertBlkInfoCache(info, bic);
|
|
auto newdata = cast(byte *)__arrayStart(info);
|
|
memcpy(newdata, px.ptr, length * sizeelem);
|
|
// do postblit processing
|
|
__doPostblit(newdata, length * sizeelem, tinext);
|
|
(cast(void **)(&px))[1] = newdata;
|
|
}
|
|
|
|
L1:
|
|
*cast(size_t *)&px = newlength;
|
|
return px;
|
|
}
|
|
|
|
|
|
/**
|
|
* Append dchar to char[]
|
|
*/
|
|
extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c)
|
|
{
|
|
// c could encode into from 1 to 4 characters
|
|
char[4] buf = void;
|
|
byte[] appendthis; // passed to appendT
|
|
if (c <= 0x7F)
|
|
{
|
|
buf.ptr[0] = cast(char)c;
|
|
appendthis = (cast(byte *)buf.ptr)[0..1];
|
|
}
|
|
else if (c <= 0x7FF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xC0 | (c >> 6));
|
|
buf.ptr[1] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..2];
|
|
}
|
|
else if (c <= 0xFFFF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xE0 | (c >> 12));
|
|
buf.ptr[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
|
|
buf.ptr[2] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..3];
|
|
}
|
|
else if (c <= 0x10FFFF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xF0 | (c >> 18));
|
|
buf.ptr[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
|
|
buf.ptr[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
|
|
buf.ptr[3] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..4];
|
|
}
|
|
else
|
|
{
|
|
import core.exception : onUnicodeError;
|
|
onUnicodeError("Invalid UTF-8 sequence", 0); // invalid utf character
|
|
}
|
|
|
|
//
|
|
// TODO: This always assumes the array type is shared, because we do not
|
|
// get a typeinfo from the compiler. Assuming shared is the safest option.
|
|
// Once the compiler is fixed, the proper typeinfo should be forwarded.
|
|
//
|
|
return _d_arrayappendT(typeid(shared char[]), x, appendthis);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import core.exception : UnicodeException;
|
|
|
|
/* Using inline try {} catch {} blocks fails to catch the UnicodeException
|
|
* thrown.
|
|
* https://issues.dlang.org/show_bug.cgi?id=16799
|
|
*/
|
|
static void assertThrown(T : Throwable = Exception, E)(lazy E expr, string msg)
|
|
{
|
|
try
|
|
expr;
|
|
catch (T e) {
|
|
assert(e.msg == msg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void f()
|
|
{
|
|
string ret;
|
|
int i = -1;
|
|
ret ~= i;
|
|
}
|
|
|
|
assertThrown!UnicodeException(f(), "Invalid UTF-8 sequence");
|
|
}
|
|
|
|
|
|
/**
|
|
* Append dchar to wchar[]
|
|
*/
|
|
extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c)
|
|
{
|
|
// c could encode into from 1 to 2 w characters
|
|
wchar[2] buf = void;
|
|
byte[] appendthis; // passed to appendT
|
|
if (c <= 0xFFFF)
|
|
{
|
|
buf.ptr[0] = cast(wchar) c;
|
|
// note that although we are passing only 1 byte here, appendT
|
|
// interprets this as being an array of wchar, making the necessary
|
|
// casts.
|
|
appendthis = (cast(byte *)buf.ptr)[0..1];
|
|
}
|
|
else
|
|
{
|
|
buf.ptr[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800);
|
|
buf.ptr[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00);
|
|
// ditto from above.
|
|
appendthis = (cast(byte *)buf.ptr)[0..2];
|
|
}
|
|
|
|
//
|
|
// TODO: This always assumes the array type is shared, because we do not
|
|
// get a typeinfo from the compiler. Assuming shared is the safest option.
|
|
// Once the compiler is fixed, the proper typeinfo should be forwarded.
|
|
//
|
|
return _d_arrayappendT(typeid(shared wchar[]), x, appendthis);
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y)
|
|
out (result)
|
|
{
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize; // array element size
|
|
debug(PRINTF) printf("_d_arraycatT(%d,%p ~ %d,%p sizeelem = %d => %d,%p)\n", x.length, x.ptr, y.length, y.ptr, sizeelem, result.length, result.ptr);
|
|
assert(result.length == x.length + y.length);
|
|
|
|
// If a postblit is involved, the contents of result might rightly differ
|
|
// from the bitwise concatenation of x and y.
|
|
if (!hasPostblit(tinext))
|
|
{
|
|
for (size_t i = 0; i < x.length * sizeelem; i++)
|
|
assert((cast(byte*)result)[i] == (cast(byte*)x)[i]);
|
|
for (size_t i = 0; i < y.length * sizeelem; i++)
|
|
assert((cast(byte*)result)[x.length * sizeelem + i] == (cast(byte*)y)[i]);
|
|
}
|
|
|
|
size_t cap = GC.sizeOf(result.ptr);
|
|
assert(!cap || cap > result.length * sizeelem);
|
|
}
|
|
body
|
|
{
|
|
version (none)
|
|
{
|
|
/* Cannot use this optimization because:
|
|
* char[] a, b;
|
|
* char c = 'a';
|
|
* b = a ~ c;
|
|
* c = 'b';
|
|
* will change the contents of b.
|
|
*/
|
|
if (!y.length)
|
|
return x;
|
|
if (!x.length)
|
|
return y;
|
|
}
|
|
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize; // array element size
|
|
debug(PRINTF) printf("_d_arraycatT(%d,%p ~ %d,%p sizeelem = %d)\n", x.length, x.ptr, y.length, y.ptr, sizeelem);
|
|
size_t xlen = x.length * sizeelem;
|
|
size_t ylen = y.length * sizeelem;
|
|
size_t len = xlen + ylen;
|
|
|
|
if (!len)
|
|
return null;
|
|
|
|
auto info = __arrayAlloc(len, ti, tinext);
|
|
byte* p = cast(byte*)__arrayStart(info);
|
|
p[len] = 0; // guessing this is to optimize for null-terminated arrays?
|
|
memcpy(p, x.ptr, xlen);
|
|
memcpy(p + xlen, y.ptr, ylen);
|
|
// do postblit processing
|
|
__doPostblit(p, xlen + ylen, tinext);
|
|
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
__setArrayAllocLength(info, len, isshared, tinext);
|
|
return p[0 .. x.length + y.length];
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) void[] _d_arraycatnTX(const TypeInfo ti, byte[][] arrs)
|
|
{
|
|
size_t length;
|
|
auto tinext = unqualify(ti.next);
|
|
auto size = tinext.tsize; // array element size
|
|
|
|
foreach (b; arrs)
|
|
length += b.length;
|
|
|
|
if (!length)
|
|
return null;
|
|
|
|
auto allocsize = length * size;
|
|
auto info = __arrayAlloc(allocsize, ti, tinext);
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
__setArrayAllocLength(info, allocsize, isshared, tinext);
|
|
void *a = __arrayStart (info);
|
|
|
|
size_t j = 0;
|
|
foreach (b; arrs)
|
|
{
|
|
if (b.length)
|
|
{
|
|
memcpy(a + j, b.ptr, b.length * size);
|
|
j += b.length * size;
|
|
}
|
|
}
|
|
|
|
// do postblit processing
|
|
__doPostblit(a, j, tinext);
|
|
|
|
return a[0..length];
|
|
}
|
|
|
|
|
|
/**
|
|
* Allocate the array, rely on the caller to do the initialization of the array.
|
|
*/
|
|
extern (C)
|
|
void* _d_arrayliteralTX(const TypeInfo ti, size_t length)
|
|
{
|
|
auto tinext = unqualify(ti.next);
|
|
auto sizeelem = tinext.tsize; // array element size
|
|
void* result;
|
|
|
|
debug(PRINTF) printf("_d_arrayliteralTX(sizeelem = %d, length = %d)\n", sizeelem, length);
|
|
if (length == 0 || sizeelem == 0)
|
|
result = null;
|
|
else
|
|
{
|
|
auto allocsize = length * sizeelem;
|
|
auto info = __arrayAlloc(allocsize, ti, tinext);
|
|
auto isshared = typeid(ti) is typeid(TypeInfo_Shared);
|
|
__setArrayAllocLength(info, allocsize, isshared, tinext);
|
|
result = __arrayStart(info);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
int[] a;
|
|
int[] b;
|
|
int i;
|
|
|
|
a = new int[3];
|
|
a[0] = 1; a[1] = 2; a[2] = 3;
|
|
b = a.dup;
|
|
assert(b.length == 3);
|
|
for (i = 0; i < 3; i++)
|
|
assert(b[i] == i + 1);
|
|
|
|
// test slice appending
|
|
b = a[0..1];
|
|
b ~= 4;
|
|
for (i = 0; i < 3; i++)
|
|
assert(a[i] == i + 1);
|
|
|
|
// test reserving
|
|
char[] arr = new char[4093];
|
|
for (i = 0; i < arr.length; i++)
|
|
arr[i] = cast(char)(i % 256);
|
|
|
|
// note that these two commands used to cause corruption, which may not be
|
|
// detected.
|
|
arr.reserve(4094);
|
|
auto arr2 = arr ~ "123";
|
|
assert(arr2[0..arr.length] == arr);
|
|
assert(arr2[arr.length..$] == "123");
|
|
|
|
// test postblit on array concat, append, length, etc.
|
|
static struct S
|
|
{
|
|
int x;
|
|
int pad;
|
|
this(this)
|
|
{
|
|
++x;
|
|
}
|
|
}
|
|
void testPostBlit(T)()
|
|
{
|
|
auto sarr = new T[1];
|
|
debug(SENTINEL) {} else
|
|
assert(sarr.capacity == 1);
|
|
|
|
// length extend
|
|
auto sarr2 = sarr;
|
|
assert(sarr[0].x == 0);
|
|
sarr2.length += 1;
|
|
assert(sarr2[0].x == 1);
|
|
assert(sarr[0].x == 0);
|
|
|
|
// append
|
|
T s;
|
|
sarr2 = sarr;
|
|
sarr2 ~= s;
|
|
assert(sarr2[0].x == 1);
|
|
assert(sarr2[1].x == 1);
|
|
assert(sarr[0].x == 0);
|
|
assert(s.x == 0);
|
|
|
|
// concat
|
|
sarr2 = sarr ~ sarr;
|
|
assert(sarr2[0].x == 1);
|
|
assert(sarr2[1].x == 1);
|
|
assert(sarr[0].x == 0);
|
|
|
|
// concat multiple (calls different method)
|
|
sarr2 = sarr ~ sarr ~ sarr;
|
|
assert(sarr2[0].x == 1);
|
|
assert(sarr2[1].x == 1);
|
|
assert(sarr2[2].x == 1);
|
|
assert(sarr[0].x == 0);
|
|
|
|
// reserve capacity
|
|
sarr2 = sarr;
|
|
sarr2.reserve(2);
|
|
assert(sarr2[0].x == 1);
|
|
assert(sarr[0].x == 0);
|
|
}
|
|
testPostBlit!(S)();
|
|
testPostBlit!(const(S))();
|
|
}
|
|
|
|
// cannot define structs inside unit test block, or they become nested structs.
|
|
version (unittest)
|
|
{
|
|
struct S1
|
|
{
|
|
int x = 5;
|
|
}
|
|
struct S2
|
|
{
|
|
int x;
|
|
this(int x) {this.x = x;}
|
|
}
|
|
struct S3
|
|
{
|
|
int[4] x;
|
|
this(int x)
|
|
{this.x[] = x;}
|
|
}
|
|
struct S4
|
|
{
|
|
int *x;
|
|
}
|
|
|
|
}
|
|
|
|
unittest
|
|
{
|
|
auto s1 = new S1;
|
|
assert(s1.x == 5);
|
|
assert(GC.getAttr(s1) == BlkAttr.NO_SCAN);
|
|
|
|
auto s2 = new S2(3);
|
|
assert(s2.x == 3);
|
|
assert(GC.getAttr(s2) == BlkAttr.NO_SCAN);
|
|
|
|
auto s3 = new S3(1);
|
|
assert(s3.x == [1,1,1,1]);
|
|
assert(GC.getAttr(s3) == BlkAttr.NO_SCAN);
|
|
debug(SENTINEL) {} else
|
|
assert(GC.sizeOf(s3) == 16);
|
|
|
|
auto s4 = new S4;
|
|
assert(s4.x == null);
|
|
assert(GC.getAttr(s4) == 0);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// Bugzilla 3454 - Inconsistent flag setting in GC.realloc()
|
|
static void test(size_t multiplier)
|
|
{
|
|
auto p = GC.malloc(8 * multiplier, BlkAttr.NO_SCAN);
|
|
assert(GC.getAttr(p) == BlkAttr.NO_SCAN);
|
|
p = GC.realloc(p, 2 * multiplier, BlkAttr.NO_SCAN);
|
|
assert(GC.getAttr(p) == BlkAttr.NO_SCAN);
|
|
}
|
|
test(1);
|
|
test(1024 * 1024);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
import core.exception;
|
|
try
|
|
{
|
|
size_t x = size_t.max;
|
|
byte[] big_buf = new byte[x];
|
|
}
|
|
catch (OutOfMemoryError)
|
|
{
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// bugzilla 13854
|
|
auto arr = new ubyte[PAGESIZE]; // ensure page size
|
|
auto info1 = GC.query(arr.ptr);
|
|
assert(info1.base !is arr.ptr); // offset is required for page size or larger
|
|
|
|
auto arr2 = arr[0..1];
|
|
assert(arr2.capacity == 0); // cannot append
|
|
arr2 ~= 0; // add a byte
|
|
assert(arr2.ptr !is arr.ptr); // reallocated
|
|
auto info2 = GC.query(arr2.ptr);
|
|
assert(info2.base is arr2.ptr); // no offset, the capacity is small.
|
|
|
|
// do the same via setting length
|
|
arr2 = arr[0..1];
|
|
assert(arr2.capacity == 0);
|
|
arr2.length += 1;
|
|
assert(arr2.ptr !is arr.ptr); // reallocated
|
|
info2 = GC.query(arr2.ptr);
|
|
assert(info2.base is arr2.ptr); // no offset, the capacity is small.
|
|
|
|
// do the same for char[] since we need a type with an initializer to test certain runtime functions
|
|
auto carr = new char[PAGESIZE];
|
|
info1 = GC.query(carr.ptr);
|
|
assert(info1.base !is carr.ptr); // offset is required for page size or larger
|
|
|
|
auto carr2 = carr[0..1];
|
|
assert(carr2.capacity == 0); // cannot append
|
|
carr2 ~= 0; // add a byte
|
|
assert(carr2.ptr !is carr.ptr); // reallocated
|
|
info2 = GC.query(carr2.ptr);
|
|
assert(info2.base is carr2.ptr); // no offset, the capacity is small.
|
|
|
|
// do the same via setting length
|
|
carr2 = carr[0..1];
|
|
assert(carr2.capacity == 0);
|
|
carr2.length += 1;
|
|
assert(carr2.ptr !is carr.ptr); // reallocated
|
|
info2 = GC.query(carr2.ptr);
|
|
assert(info2.base is carr2.ptr); // no offset, the capacity is small.
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// bugzilla 13878
|
|
auto arr = new ubyte[1];
|
|
auto info = GC.query(arr.ptr);
|
|
assert(info.attr & BlkAttr.NO_SCAN); // should be NO_SCAN
|
|
arr ~= 0; // ensure array is inserted into cache
|
|
debug(SENTINEL) {} else
|
|
assert(arr.ptr is info.base);
|
|
GC.clrAttr(arr.ptr, BlkAttr.NO_SCAN); // remove the attribute
|
|
auto arr2 = arr[0..1];
|
|
assert(arr2.capacity == 0); // cannot append
|
|
arr2 ~= 0;
|
|
assert(arr2.ptr !is arr.ptr);
|
|
info = GC.query(arr2.ptr);
|
|
assert(!(info.attr & BlkAttr.NO_SCAN)); // ensure attribute sticks
|
|
|
|
// do the same via setting length
|
|
arr = new ubyte[1];
|
|
arr ~= 0; // ensure array is inserted into cache
|
|
GC.clrAttr(arr.ptr, BlkAttr.NO_SCAN); // remove the attribute
|
|
arr2 = arr[0..1];
|
|
assert(arr2.capacity == 0);
|
|
arr2.length += 1;
|
|
assert(arr2.ptr !is arr.ptr); // reallocated
|
|
info = GC.query(arr2.ptr);
|
|
assert(!(info.attr & BlkAttr.NO_SCAN)); // ensure attribute sticks
|
|
|
|
// do the same for char[] since we need a type with an initializer to test certain runtime functions
|
|
auto carr = new char[1];
|
|
info = GC.query(carr.ptr);
|
|
assert(info.attr & BlkAttr.NO_SCAN); // should be NO_SCAN
|
|
carr ~= 0; // ensure array is inserted into cache
|
|
debug(SENTINEL) {} else
|
|
assert(carr.ptr is info.base);
|
|
GC.clrAttr(carr.ptr, BlkAttr.NO_SCAN); // remove the attribute
|
|
auto carr2 = carr[0..1];
|
|
assert(carr2.capacity == 0); // cannot append
|
|
carr2 ~= 0;
|
|
assert(carr2.ptr !is carr.ptr);
|
|
info = GC.query(carr2.ptr);
|
|
assert(!(info.attr & BlkAttr.NO_SCAN)); // ensure attribute sticks
|
|
|
|
// do the same via setting length
|
|
carr = new char[1];
|
|
carr ~= 0; // ensure array is inserted into cache
|
|
GC.clrAttr(carr.ptr, BlkAttr.NO_SCAN); // remove the attribute
|
|
carr2 = carr[0..1];
|
|
assert(carr2.capacity == 0);
|
|
carr2.length += 1;
|
|
assert(carr2.ptr !is carr.ptr); // reallocated
|
|
info = GC.query(carr2.ptr);
|
|
assert(!(info.attr & BlkAttr.NO_SCAN)); // ensure attribute sticks
|
|
}
|
|
|
|
// test struct finalizers
|
|
debug(SENTINEL) {} else
|
|
unittest
|
|
{
|
|
__gshared int dtorCount;
|
|
static struct S1
|
|
{
|
|
int x;
|
|
|
|
~this()
|
|
{
|
|
dtorCount++;
|
|
}
|
|
}
|
|
|
|
dtorCount = 0;
|
|
S1* s1 = new S1;
|
|
delete s1;
|
|
assert(dtorCount == 1);
|
|
|
|
dtorCount = 0;
|
|
S1[] arr1 = new S1[7];
|
|
delete arr1;
|
|
assert(dtorCount == 7);
|
|
|
|
if (callStructDtorsDuringGC)
|
|
{
|
|
dtorCount = 0;
|
|
S1* s2 = new S1;
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
assert(dtorCount == 1);
|
|
GC.free(s2);
|
|
|
|
dtorCount = 0;
|
|
const(S1)* s3 = new const(S1);
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
assert(dtorCount == 1);
|
|
GC.free(cast(void*)s3);
|
|
|
|
dtorCount = 0;
|
|
shared(S1)* s4 = new shared(S1);
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
assert(dtorCount == 1);
|
|
GC.free(cast(void*)s4);
|
|
|
|
dtorCount = 0;
|
|
const(S1)[] carr1 = new const(S1)[5];
|
|
BlkInfo blkinf1 = GC.query(carr1.ptr);
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
assert(dtorCount == 5);
|
|
GC.free(blkinf1.base);
|
|
}
|
|
|
|
dtorCount = 0;
|
|
S1[] arr2 = new S1[10];
|
|
arr2.length = 6;
|
|
arr2.assumeSafeAppend;
|
|
assert(dtorCount == 4); // destructors run explicitely?
|
|
|
|
if (callStructDtorsDuringGC)
|
|
{
|
|
dtorCount = 0;
|
|
BlkInfo blkinf = GC.query(arr2.ptr);
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
assert(dtorCount == 6);
|
|
GC.free(blkinf.base);
|
|
}
|
|
|
|
// associative arrays
|
|
import rt.aaA : entryDtor;
|
|
// throw away all existing AA entries with dtor
|
|
GC.runFinalizers((cast(char*)(&entryDtor))[0..1]);
|
|
|
|
S1[int] aa1;
|
|
aa1[0] = S1(0);
|
|
aa1[1] = S1(1);
|
|
if (callStructDtorsDuringGC)
|
|
{
|
|
dtorCount = 0;
|
|
aa1 = null;
|
|
GC.runFinalizers((cast(char*)(&entryDtor))[0..1]);
|
|
assert(dtorCount == 2);
|
|
}
|
|
|
|
int[S1] aa2;
|
|
aa2[S1(0)] = 0;
|
|
aa2[S1(1)] = 1;
|
|
aa2[S1(2)] = 2;
|
|
if (callStructDtorsDuringGC)
|
|
{
|
|
dtorCount = 0;
|
|
aa2 = null;
|
|
GC.runFinalizers((cast(char*)(&entryDtor))[0..1]);
|
|
assert(dtorCount == 3);
|
|
}
|
|
|
|
S1[2][int] aa3;
|
|
aa3[0] = [S1(0),S1(2)];
|
|
aa3[1] = [S1(1),S1(3)];
|
|
if (callStructDtorsDuringGC)
|
|
{
|
|
dtorCount = 0;
|
|
aa3 = null;
|
|
GC.runFinalizers((cast(char*)(&entryDtor))[0..1]);
|
|
assert(dtorCount == 4);
|
|
}
|
|
}
|
|
|
|
// test class finalizers exception handling
|
|
unittest
|
|
{
|
|
bool test(E)()
|
|
{
|
|
import core.exception;
|
|
static class C1
|
|
{
|
|
E exc;
|
|
this(E exc) { this.exc = exc; }
|
|
~this() { throw exc; }
|
|
}
|
|
|
|
bool caught = false;
|
|
C1 c = new C1(new E("test onFinalizeError"));
|
|
try
|
|
{
|
|
GC.runFinalizers((cast(uint*)&C1.__dtor)[0..1]);
|
|
}
|
|
catch (FinalizeError err)
|
|
{
|
|
caught = true;
|
|
}
|
|
catch (E)
|
|
{
|
|
}
|
|
GC.free(cast(void*)c);
|
|
return caught;
|
|
}
|
|
|
|
assert( test!Exception);
|
|
import core.exception : InvalidMemoryOperationError;
|
|
assert(!test!InvalidMemoryOperationError);
|
|
}
|
|
|
|
// test struct finalizers exception handling
|
|
debug(SENTINEL) {} else
|
|
unittest
|
|
{
|
|
if (!callStructDtorsDuringGC)
|
|
return;
|
|
|
|
bool test(E)()
|
|
{
|
|
import core.exception;
|
|
static struct S1
|
|
{
|
|
E exc;
|
|
~this() { throw exc; }
|
|
}
|
|
|
|
bool caught = false;
|
|
S1* s = new S1(new E("test onFinalizeError"));
|
|
try
|
|
{
|
|
GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]);
|
|
}
|
|
catch (FinalizeError err)
|
|
{
|
|
caught = true;
|
|
}
|
|
catch (E)
|
|
{
|
|
}
|
|
GC.free(s);
|
|
return caught;
|
|
}
|
|
|
|
assert( test!Exception);
|
|
import core.exception : InvalidMemoryOperationError;
|
|
assert(!test!InvalidMemoryOperationError);
|
|
}
|
|
|
|
// test bug 14126
|
|
unittest
|
|
{
|
|
static struct S
|
|
{
|
|
S* thisptr;
|
|
~this() { assert(&this == thisptr); thisptr = null;}
|
|
}
|
|
|
|
S[] test14126 = new S[2048]; // make sure we allocate at least a PAGE
|
|
foreach (ref s; test14126)
|
|
{
|
|
s.thisptr = &s;
|
|
}
|
|
}
|