| /** |
| * 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 rt/_lifetime.d) |
| */ |
| |
| module rt.lifetime; |
| |
| import core.attribute : weak; |
| import core.checkedint : mulu; |
| import core.exception : onFinalizeError, onOutOfMemoryError, onUnicodeError; |
| import core.internal.gc.blockmeta : PAGESIZE; |
| import core.memory; |
| import core.stdc.stdlib : malloc; |
| import core.stdc.string : memcpy, memset; |
| static import rt.tlsgc; |
| |
| debug (PRINTF) import core.stdc.stdio : printf; |
| debug (VALGRIND) import etc.valgrind.valgrind; |
| |
| alias BlkAttr = GC.BlkAttr; |
| |
| // for now, all GC array functions are not exposed via core.memory. |
| extern(C) { |
| void[] gc_getArrayUsed(void *ptr, bool atomic) nothrow; |
| bool gc_expandArrayUsed(void[] slice, size_t newUsed, bool atomic) nothrow; |
| size_t gc_reserveArrayCapacity(void[] slice, size_t request, bool atomic) nothrow; |
| bool gc_shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic) nothrow; |
| } |
| |
| private |
| { |
| alias bool function(Object) CollectHandler; |
| __gshared CollectHandler collectHandler = null; |
| |
| extern (C) void _d_monitordelete(Object h, bool det); |
| |
| } |
| |
| // Now-removed symbol, kept around for ABI |
| // Some programs are dynamically linked, so best to err on the side of keeping symbols around for a while (especially extern(C) ones) |
| // https://github.com/dlang/druntime/pull/3361 |
| deprecated extern (C) void lifetime_init() |
| { |
| } |
| |
| /** |
| Allocate memory using the garbage collector |
| |
| DMD uses this to allocate closures: |
| --- |
| void f(byte[24] x) |
| { |
| return () => x; // `x` is on stack, must be moved to heap to keep it alive |
| } |
| --- |
| |
| Params: |
| sz = number of bytes to allocate |
| |
| Returns: pointer to `sz` bytes of free, uninitialized memory, managed by the GC. |
| */ |
| extern (C) void* _d_allocmemory(size_t sz) @weak |
| { |
| return GC.malloc(sz); |
| } |
| |
| /** |
| Create a new class instance. |
| |
| Allocates memory and sets fields to their initial value, but does not call a constructor. |
| |
| --- |
| new Object() // _d_newclass(typeid(Object)) |
| --- |
| Params: |
| ci = `TypeInfo_Class` object, to provide instance size and initial bytes to copy |
| |
| Returns: newly created object |
| */ |
| extern (C) Object _d_newclass(const ClassInfo ci) @weak |
| { |
| void* p; |
| auto init = ci.initializer; |
| |
| 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(init.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(init.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, init.ptr, cast(ulong)init.length); |
| printf("vptr = %p\n", *cast(void**) init); |
| printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); |
| printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); |
| printf("init[0] = %x\n", (cast(uint*) init)[0]); |
| printf("init[1] = %x\n", (cast(uint*) init)[1]); |
| printf("init[2] = %x\n", (cast(uint*) init)[2]); |
| printf("init[3] = %x\n", (cast(uint*) init)[3]); |
| printf("init[4] = %x\n", (cast(uint*) init)[4]); |
| } |
| |
| // initialize it |
| p[0 .. init.length] = cast(void[]) init[]; |
| |
| 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) @weak |
| { |
| 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; |
| } |
| } |
| |
| // strip const/immutable/shared/inout from type info |
| inout(TypeInfo) unqualify(return scope 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; |
| } |
| |
| private uint __typeAttrs(const scope TypeInfo ti, void *copyAttrsFrom = null) pure nothrow |
| { |
| if (copyAttrsFrom) |
| { |
| // try to copy attrs from the given block |
| auto info = GC.query(copyAttrsFrom); |
| if (info.base) |
| return info.attr; |
| } |
| uint attrs = !(ti.flags & 1) ? BlkAttr.NO_SCAN : 0; |
| if (typeid(ti) is typeid(TypeInfo_Struct)) { |
| auto sti = cast(TypeInfo_Struct)cast(void*)ti; |
| if (sti.xdtor) |
| attrs |= BlkAttr.FINALIZE; |
| } |
| return attrs; |
| } |
| |
| /** |
| 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. |
| |
| Params: |
| ti = `TypeInfo` of array type |
| arr = array to shrink. Its `.length` is element length, not byte length, despite `void` type |
| */ |
| extern(C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) nothrow |
| { |
| debug(PRINTF) printf("_d_arrayshrinkfit, elemsize = %zd, arr.ptr = %p arr.length = %zd\n", ti.next.tsize, arr.ptr, arr.length); |
| auto tinext = unqualify(ti.next); |
| auto size = tinext.tsize; // array element size |
| auto reqsize = arr.length * size; |
| auto isshared = typeid(ti) is typeid(TypeInfo_Shared); |
| |
| auto curArr = gc_getArrayUsed(arr.ptr, isshared); |
| if (curArr.ptr is null) |
| // not a valid GC pointer |
| return; |
| |
| // align the array. |
| auto offset = arr.ptr - curArr.ptr; |
| auto cursize = curArr.length - offset; |
| if (cursize <= reqsize) |
| // invalid situation, or no change. |
| return; |
| |
| // if the type has a destructor, destroy elements we are about to remove. |
| if (typeid(tinext) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast |
| { |
| auto sti = cast(TypeInfo_Struct)cast(void*)tinext; |
| if (sti.xdtor) |
| { |
| try |
| { |
| finalize_array(arr.ptr + reqsize, cursize - reqsize, sti); |
| } |
| catch (Exception e) |
| { |
| onFinalizeError(sti, e); |
| } |
| } |
| } |
| |
| gc_shrinkArrayUsed(arr.ptr[0 .. reqsize], cursize, isshared); |
| } |
| |
| package bool hasPostblit(in TypeInfo ti) nothrow pure |
| { |
| 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. |
| |
| Params: |
| ti = type info of element type |
| newcapacity = requested new capacity |
| p = pointer to array to set. Its `length` is left unchanged. |
| |
| 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) @weak |
| in |
| { |
| assert(ti); |
| assert(!(*p).length || (*p).ptr); |
| } |
| do |
| { |
| auto isshared = typeid(ti) is typeid(TypeInfo_Shared); |
| 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 |
| { |
| bool overflow = false; |
| size_t reqsize = mulu(size, newcapacity, overflow); |
| if (!overflow) |
| goto Lcontinue; |
| } |
| Loverflow: |
| onOutOfMemoryError(); |
| assert(0); |
| Lcontinue: |
| |
| // step 1, see if we can ensure the capacity is valid in-place |
| auto datasize = (*p).length * size; |
| auto curCapacity = gc_reserveArrayCapacity((*p).ptr[0 .. datasize], reqsize, isshared); |
| if (curCapacity != 0) |
| // in-place worked! |
| return curCapacity / size; |
| |
| if (reqsize <= datasize) |
| // requested size is less than array size, the current array satisfies |
| // the request. But this is not an appendable GC array, so return 0. |
| return 0; |
| |
| // step 2, if reserving in-place doesn't work, allocate a new array with at |
| // least the requested allocated size. |
| auto attrs = __typeAttrs(tinext, (*p).ptr) | BlkAttr.APPENDABLE; |
| auto ptr = GC.malloc(reqsize, attrs, tinext); |
| if (ptr is null) |
| goto Loverflow; |
| |
| // copy the data over. |
| // note that malloc will have initialized the data we did not request to 0. |
| memcpy(ptr, (*p).ptr, datasize); |
| |
| // handle postblit |
| __doPostblit(ptr, datasize, tinext); |
| |
| if (!(attrs & BlkAttr.NO_SCAN)) |
| { |
| // need to memset the newly requested data, except for the data that |
| // malloc returned that we didn't request. |
| void *endptr = ptr + reqsize; |
| void *begptr = ptr + datasize; |
| |
| // sanity check |
| assert(endptr >= begptr); |
| memset(begptr, 0, endptr - begptr); |
| } |
| |
| *p = ptr[0 .. (*p).length]; |
| |
| // set up the correct length. Note that we need to do this here, because |
| // the GC malloc will automatically set the used size to what we requested. |
| gc_shrinkArrayUsed(ptr[0 .. datasize], reqsize, isshared); |
| |
| curCapacity = gc_reserveArrayCapacity(ptr[0 .. datasize], 0, isshared); |
| assert(curCapacity); |
| return curCapacity / size; |
| } |
| |
| /** |
| Allocate an array with the garbage collector. |
| |
| Has three variants: |
| - `_d_newarrayU` leave elements uninitialized |
| - `_d_newarrayT` initializes to 0 (e.g `new int[]`) |
| - `_d_newarrayiT` initializes based on initializer retrieved from TypeInfo (e.g `new float[]`) |
| |
| Params: |
| ti = the type of the resulting array, (may also be the corresponding `array.ptr` type) |
| length = `.length` of resulting array |
| Returns: newly allocated array |
| */ |
| extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length) pure nothrow @weak |
| { |
| auto tinext = unqualify(ti.next); |
| auto size = tinext.tsize; |
| |
| debug(PRINTF) printf("_d_newarrayU(length = x%zx, size = %zd)\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 |
| { |
| bool overflow = false; |
| size = mulu(size, length, overflow); |
| if (!overflow) |
| goto Lcontinue; |
| } |
| Loverflow: |
| onOutOfMemoryError(); |
| assert(0); |
| Lcontinue: |
| |
| auto ptr = GC.malloc(size, __typeAttrs(tinext) | BlkAttr.APPENDABLE, tinext); |
| if (!ptr) |
| goto Loverflow; |
| debug(PRINTF) printf(" p = %p\n", ptr); |
| return ptr[0 .. length]; |
| } |
| |
| /// ditto |
| extern (C) void[] _d_newarrayT(const TypeInfo ti, size_t length) pure nothrow @weak |
| { |
| void[] result = _d_newarrayU(ti, length); |
| auto tinext = unqualify(ti.next); |
| auto size = tinext.tsize; |
| |
| memset(result.ptr, 0, size * length); |
| return result; |
| } |
| |
| /// ditto |
| extern (C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) pure nothrow @weak |
| { |
| import core.internal.traits : AliasSeq; |
| |
| void[] result = _d_newarrayU(ti, length); |
| auto tinext = unqualify(ti.next); |
| auto size = tinext.tsize; |
| |
| auto init = tinext.initializer(); |
| |
| switch (init.length) |
| { |
| foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) |
| { |
| case T.sizeof: |
| if (tinext.talign % T.alignof == 0) |
| { |
| (cast(T*)result.ptr)[0 .. size * length / T.sizeof] = *cast(T*)init.ptr; |
| return result; |
| } |
| goto default; |
| } |
| |
| default: |
| { |
| immutable sz = init.length; |
| for (size_t u = 0; u < size * length; u += sz) |
| memcpy(result.ptr + u, init.ptr, sz); |
| return result; |
| } |
| } |
| } |
| |
| /** |
| Non-template version of $(REF _d_newitemT, core,lifetime) that does not perform |
| initialization. Needed for $(REF allocEntry, rt,aaA). |
| |
| Params: |
| _ti = `TypeInfo` of item to allocate |
| Returns: |
| newly allocated item |
| */ |
| extern (C) void* _d_newitemU(scope const TypeInfo _ti) pure nothrow @weak |
| { |
| auto ti = unqualify(_ti); |
| auto flags = __typeAttrs(ti); |
| |
| return GC.malloc(ti.tsize, flags, ti); |
| } |
| |
| /** |
| * |
| */ |
| extern (C) void _d_delmemory(void* *p) @weak |
| { |
| if (*p) |
| { |
| GC.free(*p); |
| *p = null; |
| } |
| } |
| |
| |
| /** |
| * |
| */ |
| extern (C) void _d_callinterfacefinalizer(void *p) @weak |
| { |
| 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) @weak |
| { |
| 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, TypeInfo typeInfo, scope const(void)[] segment) nothrow |
| { |
| if (!p) |
| return false; |
| |
| if (typeInfo !is null) |
| { |
| assert(typeid(typeInfo) is typeid(TypeInfo_Struct)); |
| |
| auto ti = cast(TypeInfo_Struct)cast(void*)typeInfo; |
| return cast(size_t)(cast(void*)ti.xdtor - segment.ptr) < segment.length; |
| } |
| |
| // otherwise class |
| auto ppv = cast(void**) p; |
| if (!*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; |
| } |
| |
| 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, TypeInfo_Struct ti) nothrow |
| { |
| debug(PRINTF) printf("finalize_struct(p = %p)\n", p); |
| |
| 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] = cast(void[]) w[]; |
| } |
| } |
| catch (Exception e) |
| { |
| onFinalizeError(*pc, e); |
| } |
| finally |
| { |
| *ppv = null; // zero vptr even if `resetMemory` is false |
| } |
| } |
| |
| /// Backwards compatibility |
| extern (C) void rt_finalize(void* p, bool det = true) nothrow |
| { |
| rt_finalize2(p, det, true); |
| } |
| |
| extern (C) void rt_finalizeFromGC(void* p, size_t size, uint attr, TypeInfo typeInfo) nothrow |
| { |
| // to verify: reset memory necessary? |
| if (typeInfo is null) { |
| rt_finalize2(p, false, false); // class |
| return; |
| } |
| |
| assert(typeid(typeInfo) is typeid(TypeInfo_Struct)); |
| |
| auto si = cast(TypeInfo_Struct)cast(void*)typeInfo; |
| |
| try |
| { |
| if (attr & BlkAttr.APPENDABLE) |
| { |
| finalize_array(p, size, si); |
| } |
| else |
| finalize_struct(p, si); // struct |
| } |
| catch (Exception e) |
| { |
| onFinalizeError(si, e); |
| } |
| } |
| |
| |
| /** |
| Resize a dynamic array by setting the `.length` property |
| |
| Newly created elements are initialized to their default value. |
| |
| Has two variants: |
| - `_d_arraysetlengthT` for arrays with elements that initialize to 0 |
| - `_d_arraysetlengthiT` for non-zero initializers retrieved from `TypeInfo` |
| |
| --- |
| void main() |
| { |
| int[] a = [1, 2]; |
| a.length = 3; // gets lowered to `_d_arraysetlengthT(typeid(int[]), 3, &a)` |
| } |
| --- |
| |
| Params: |
| ti = `TypeInfo` of array |
| newlength = new value for the array's `.length` |
| p = pointer to array to update the `.length` of. |
| While it's cast to `void[]`, its `.length` is still treated as element length. |
| Returns: `*p` after being updated |
| */ |
| extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p) @weak |
| in |
| { |
| assert(ti); |
| assert(!(*p).length || (*p).ptr); |
| } |
| do |
| { |
| debug(PRINTF) |
| { |
| //printf("_d_arraysetlengthT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength); |
| if (p) |
| printf("\tp.ptr = %p, p.length = %zd\n", (*p).ptr, (*p).length); |
| } |
| |
| if (newlength <= (*p).length) |
| { |
| *p = (*p)[0 .. newlength]; |
| return *p; |
| } |
| auto tinext = unqualify(ti.next); |
| size_t sizeelem = tinext.tsize; |
| |
| /* Calculate: newsize = newlength * sizeelem |
| */ |
| bool overflow = false; |
| version (D_InlineAsm_X86) |
| { |
| size_t newsize = void; |
| |
| asm pure nothrow @nogc |
| { |
| mov EAX, newlength; |
| mul EAX, sizeelem; |
| mov newsize, EAX; |
| setc overflow; |
| } |
| } |
| else version (D_InlineAsm_X86_64) |
| { |
| size_t newsize = void; |
| |
| asm pure nothrow @nogc |
| { |
| mov RAX, newlength; |
| mul RAX, sizeelem; |
| mov newsize, RAX; |
| setc overflow; |
| } |
| } |
| else |
| { |
| const size_t newsize = mulu(sizeelem, newlength, overflow); |
| } |
| if (overflow) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| debug(PRINTF) printf("newsize = %zx, newlength = %zx\n", newsize, newlength); |
| |
| if (!(*p).ptr) |
| { |
| assert((*p).length == 0); |
| // pointer was null, need to allocate |
| auto ptr = GC.malloc(newsize, __typeAttrs(tinext) | BlkAttr.APPENDABLE, tinext); |
| if (ptr is null) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| memset(ptr, 0, newsize); |
| *p = ptr[0 .. newlength]; |
| return *p; |
| } |
| |
| const size_t size = (*p).length * sizeelem; |
| const isshared = typeid(ti) is typeid(TypeInfo_Shared); |
| |
| /* Attempt to extend past the end of the existing array. |
| * If not possible, allocate new space for entire array and copy. |
| */ |
| void* newdata = (*p).ptr; |
| if (!gc_expandArrayUsed(newdata[0 .. size], newsize, isshared)) |
| { |
| newdata = GC.malloc(newsize, __typeAttrs(tinext, (*p).ptr) | BlkAttr.APPENDABLE, tinext); |
| if (newdata is null) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| newdata[0 .. size] = (*p).ptr[0 .. size]; |
| |
| // Do postblit processing, as we are making a copy. |
| __doPostblit(newdata, size, tinext); |
| } |
| |
| // Zero the unused portion of the newly allocated space |
| memset(newdata + size, 0, newsize - size); |
| |
| *p = newdata[0 .. newlength]; |
| return *p; |
| } |
| |
| /// ditto |
| extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p) @weak |
| in |
| { |
| assert(!(*p).length || (*p).ptr); |
| } |
| do |
| { |
| debug(PRINTF) |
| { |
| //printf("_d_arraysetlengthT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength); |
| if (p) |
| printf("\tp.ptr = %p, p.length = %zd\n", (*p).ptr, (*p).length); |
| } |
| |
| if (newlength <= (*p).length) |
| { |
| *p = (*p)[0 .. newlength]; |
| return *p; |
| } |
| auto tinext = unqualify(ti.next); |
| size_t sizeelem = tinext.tsize; |
| |
| /* Calculate: newsize = newlength * sizeelem |
| */ |
| bool overflow = false; |
| version (D_InlineAsm_X86) |
| { |
| size_t newsize = void; |
| |
| asm pure nothrow @nogc |
| { |
| mov EAX, newlength; |
| mul EAX, sizeelem; |
| mov newsize, EAX; |
| setc overflow; |
| } |
| } |
| else version (D_InlineAsm_X86_64) |
| { |
| size_t newsize = void; |
| |
| asm pure nothrow @nogc |
| { |
| mov RAX, newlength; |
| mul RAX, sizeelem; |
| mov newsize, RAX; |
| setc overflow; |
| } |
| } |
| else |
| { |
| const size_t newsize = mulu(sizeelem, newlength, overflow); |
| } |
| if (overflow) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| debug(PRINTF) printf("newsize = %zx, newlength = %zx\n", newsize, newlength); |
| |
| static void doInitialize(void *start, void *end, const void[] initializer) |
| { |
| if (initializer.length == 1) |
| { |
| memset(start, *(cast(ubyte*)initializer.ptr), end - start); |
| } |
| else |
| { |
| auto q = initializer.ptr; |
| immutable initsize = initializer.length; |
| for (; start < end; start += initsize) |
| { |
| memcpy(start, q, initsize); |
| } |
| } |
| } |
| |
| if (!(*p).ptr) |
| { |
| assert((*p).length == 0); |
| // pointer was null, need to allocate |
| auto ptr = GC.malloc(newsize, __typeAttrs(tinext) | BlkAttr.APPENDABLE, tinext); |
| if (ptr is null) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| doInitialize(ptr, ptr + newsize, tinext.initializer); |
| *p = ptr[0 .. newlength]; |
| return *p; |
| } |
| |
| const size_t size = (*p).length * sizeelem; |
| const isshared = typeid(ti) is typeid(TypeInfo_Shared); |
| |
| /* Attempt to extend past the end of the existing array. |
| * If not possible, allocate new space for entire array and copy. |
| */ |
| void* newdata = (*p).ptr; |
| if (!gc_expandArrayUsed(newdata[0 .. size], newsize, isshared)) |
| { |
| newdata = GC.malloc(newsize, __typeAttrs(tinext, (*p).ptr) | BlkAttr.APPENDABLE, tinext); |
| if (newdata is null) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| newdata[0 .. size] = (*p).ptr[0 .. size]; |
| |
| // Do postblit processing, as we are making a copy. |
| __doPostblit(newdata, size, tinext); |
| } |
| |
| // Initialize the unused portion of the newly allocated space |
| doInitialize(newdata + size, newdata + newsize, tinext.initializer); |
| *p = newdata[0 .. newlength]; |
| return *p; |
| } |
| |
| |
| /** |
| Given an array of length `size` that needs to be expanded to `newlength`, |
| compute a new capacity. |
| |
| Better version by Dave Fladebo, enhanced by Steven Schveighoffer: |
| This uses an inverse logorithmic algorithm to pre-allocate a bit more |
| space for larger arrays. |
| - The maximum "extra" space is about 80% of the requested space. This is for |
| PAGE size and smaller. |
| - As the arrays grow, the relative pre-allocated space shrinks. |
| - Perhaps most importantly, overall memory usage and stress on the GC |
| is decreased significantly for demanding environments. |
| - The algorithm is tuned to avoid any division at runtime. |
| |
| Params: |
| newlength = new `.length` |
| elemsize = size of the element in the new array |
| Returns: new capacity for array |
| */ |
| size_t newCapacity(size_t newlength, size_t elemsize) |
| { |
| size_t newcap = newlength * elemsize; |
| |
| /* |
| * Max growth factor numerator is 234, so allow for multiplying by 256. |
| * But also, the resulting size cannot be more than 2x, so prevent |
| * growing if 2x would fill up the address space (for 32-bit) |
| */ |
| enum largestAllowed = (ulong.max >> 8) & (size_t.max >> 1); |
| if (!newcap || (newcap & ~largestAllowed)) |
| return newcap; |
| |
| /* |
| * The calculation for "extra" space depends on the requested capacity. |
| * We use an inverse logarithm of the new capacity to add an extra 15% |
| * to 83% capacity. Note that normally we humans think in terms of |
| * percent, but using 128 instead of 100 for the denominator means we |
| * can avoid all division by simply bit-shifthing. Since there are only |
| * 64 bits in a long, the bsr of a size_t is going to be 0 - 63. Using |
| * a lookup table allows us to precalculate the multiplier based on the |
| * inverse logarithm. The formula rougly is: |
| * |
| * newcap = request * (1.0 + min(0.83, 10.0 / (log(request) + 1))) |
| */ |
| import core.bitop; |
| static immutable multTable = (){ |
| assert(__ctfe); |
| ulong[size_t.sizeof * 8] result; |
| foreach (i; 0 .. result.length) |
| { |
| auto factor = 128 + 1280 / (i + 1); |
| result[i] = factor > 234 ? 234 : factor; |
| } |
| return result; |
| }(); |
| |
| auto mult = multTable[bsr(newcap)]; |
| |
| // if this were per cent, then the code would look like: |
| // ((newlength * mult + 99) / 100) * elemsize |
| newcap = cast(size_t)((newlength * mult + 127) >> 7) * elemsize; |
| debug(PRINTF) printf("mult: %2.2f, alloc: %2.2f\n",mult/128.0,newcap / cast(double)elemsize); |
| debug(PRINTF) printf("newcap = %zd, newlength = %zd, elemsize = %zd\n", newcap, newlength, elemsize); |
| return newcap; |
| } |
| |
| |
| /** |
| Extend an array by n elements. |
| |
| Caller must initialize those elements. |
| |
| Params: |
| ti = type info of array type (not element type) |
| px = array to append to, cast to `byte[]` while keeping the same `.length`. Will be updated. |
| n = number of elements to append |
| Returns: `px` after being appended to |
| */ |
| extern (C) |
| byte[] _d_arrayappendcTX(const TypeInfo ti, return scope ref byte[] px, size_t n) @weak |
| { |
| // This is a cut&paste job from _d_arrayappendT(). Should be refactored. |
| |
| // Short circuit if no data is being appended. |
| if (n == 0) |
| return px; |
| |
| |
| // 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 length = px.length; |
| auto newlength = length + n; |
| auto newsize = newlength * sizeelem; |
| auto size = length * sizeelem; |
| |
| if (!gc_expandArrayUsed(px.ptr[0 .. size], newsize, isshared)) |
| { |
| // could not set the size, we must reallocate. |
| auto newcap = newCapacity(newlength, sizeelem); |
| auto attrs = __typeAttrs(tinext, px.ptr) | BlkAttr.APPENDABLE; |
| auto ptr = cast(byte*) GC.malloc(newcap, attrs, tinext); |
| if (ptr is null) |
| { |
| onOutOfMemoryError(); |
| assert(0); |
| } |
| |
| if (newsize != newcap) |
| { |
| // For small blocks that are always fully scanned, if we allocated more |
| // capacity than was requested, we are responsible for zeroing that |
| // memory. |
| // TODO: should let the GC figure this out, as this property may |
| // not always hold. |
| if (!(attrs & BlkAttr.NO_SCAN) && newcap < PAGESIZE) |
| memset(ptr + newsize, 0, newcap - newsize); |
| |
| gc_shrinkArrayUsed(ptr[0 .. newsize], newcap, isshared); |
| } |
| |
| memcpy(ptr, px.ptr, size); |
| |
| // do potsblit processing. |
| __doPostblit(ptr, size, tinext); |
| |
| px = ptr[0 .. newlength]; |
| return px; |
| } |
| |
| // we were able to expand in place, just update the length |
| px = px.ptr[0 .. newlength]; |
| return px; |
| } |
| |
| |
| /** |
| Append `dchar` to `char[]`, converting UTF-32 to UTF-8 |
| |
| --- |
| void main() |
| { |
| char[] s; |
| s ~= 'α'; |
| } |
| --- |
| |
| Params: |
| x = array to append to cast to `byte[]`. Will be modified. |
| c = `dchar` to append |
| Returns: updated `x` cast to `void[]` |
| */ |
| extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c) @weak |
| { |
| // c could encode into from 1 to 4 characters |
| char[4] buf = void; |
| char[] appendthis; // passed to appendT |
| if (c <= 0x7F) |
| { |
| buf.ptr[0] = cast(char)c; |
| appendthis = buf[0..1]; |
| } |
| else if (c <= 0x7FF) |
| { |
| buf.ptr[0] = cast(char)(0xC0 | (c >> 6)); |
| buf.ptr[1] = cast(char)(0x80 | (c & 0x3F)); |
| appendthis = buf[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 = buf[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 = buf[0..4]; |
| } |
| else |
| { |
| 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. |
| // |
| |
| // Hack because _d_arrayappendT takes `x` as a reference |
| auto xx = cast(shared(char)[])x; |
| object._d_arrayappendT(xx, cast(shared(char)[])appendthis); |
| x = cast(byte[])xx; |
| return x; |
| } |
| |
| 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[]`, converting UTF-32 to UTF-16 |
| |
| --- |
| void main() |
| { |
| dchar x; |
| wchar[] s; |
| s ~= 'α'; |
| } |
| --- |
| |
| Params: |
| x = array to append to cast to `byte[]`. Will be modified. |
| c = `dchar` to append |
| |
| Returns: updated `x` cast to `void[]` |
| */ |
| extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c) @weak |
| { |
| // c could encode into from 1 to 2 w characters |
| wchar[2] buf = void; |
| wchar[] appendthis; // passed to appendT |
| if (c <= 0xFFFF) |
| { |
| buf.ptr[0] = cast(wchar) c; |
| appendthis = buf[0..1]; |
| } |
| else |
| { |
| buf.ptr[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); |
| buf.ptr[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); |
| appendthis = buf[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. |
| // |
| |
| auto xx = (cast(shared(wchar)*)x.ptr)[0 .. x.length]; |
| object._d_arrayappendT(xx, cast(shared(wchar)[])appendthis); |
| x = (cast(byte*)xx.ptr)[0 .. xx.length]; |
| return x; |
| } |
| |
| /** |
| Allocate an array literal |
| |
| Rely on the caller to do the initialization of the array. |
| |
| --- |
| int[] getArr() |
| { |
| return [10, 20]; |
| // auto res = cast(int*) _d_arrayliteralTX(typeid(int[]), 2); |
| // res[0] = 10; |
| // res[1] = 20; |
| // return res[0..2]; |
| } |
| --- |
| |
| Params: |
| ti = `TypeInfo` of resulting array type |
| length = `.length` of array literal |
| |
| Returns: pointer to allocated array |
| */ |
| extern (C) |
| void* _d_arrayliteralTX(const TypeInfo ti, size_t length) @weak |
| { |
| auto tinext = unqualify(ti.next); |
| auto sizeelem = tinext.tsize; // array element size |
| void* result; |
| |
| debug(PRINTF) printf("_d_arrayliteralTX(sizeelem = %zd, length = %zd)\n", sizeelem, length); |
| if (length == 0 || sizeelem == 0) |
| return null; |
| else |
| { |
| auto allocsize = length * sizeelem; |
| return GC.malloc(allocsize, __typeAttrs(tinext) | BlkAttr.APPENDABLE, tinext); |
| } |
| } |
| |
| |
| 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))(); |
| } |
| |
| unittest |
| { |
| // Bugzilla 3454 - Inconsistent flag setting in GC.realloc() |
| static void test(size_t multiplier) |
| { |
| auto p = GC.malloc(8 * multiplier, 0); |
| assert(GC.getAttr(p) == 0); |
| |
| // no move, set attr |
| p = GC.realloc(p, 8 * multiplier + 5, BlkAttr.NO_SCAN); |
| assert(GC.getAttr(p) == BlkAttr.NO_SCAN); |
| |
| // shrink, copy attr |
| p = GC.realloc(p, 2 * multiplier, 0); |
| assert(GC.getAttr(p) == BlkAttr.NO_SCAN); |
| |
| // extend, copy attr |
| p = GC.realloc(p, 8 * multiplier, 0); |
| assert(GC.getAttr(p) == BlkAttr.NO_SCAN); |
| } |
| test(16); |
| version (OnlyLowMemUnittests) {} else |
| test(1024 * 1024); |
| } |
| |
| unittest |
| { |
| import core.exception; |
| try |
| { |
| size_t x = size_t.max; |
| byte[] big_buf = new byte[x]; |
| } |
| catch (OutOfMemoryError) |
| { |
| } |
| } |
| |
| unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=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 |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=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 |
| deprecated unittest |
| { |
| __gshared int dtorCount; |
| static struct S1 |
| { |
| int x; |
| |
| ~this() |
| { |
| dtorCount++; |
| } |
| } |
| |
| 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]; |
| auto 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? |
| |
| dtorCount = 0; |
| auto blkinf = GC.query(arr2.ptr); |
| GC.runFinalizers((cast(char*)(typeid(S1).xdtor))[0..1]); |
| assert(dtorCount == 6); |
| GC.free(blkinf.base); |
| |
| // associative arrays |
| S1[int] aa1; |
| aa1[0] = S1(0); |
| aa1[1] = S1(1); |
| dtorCount = 0; |
| aa1 = null; |
| auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, S1)).xdtor; |
| GC.runFinalizers((cast(char*)dtor1)[0..1]); |
| assert(dtorCount == 2); |
| |
| int[S1] aa2; |
| aa2[S1(0)] = 0; |
| aa2[S1(1)] = 1; |
| aa2[S1(2)] = 2; |
| dtorCount = 0; |
| aa2 = null; |
| auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(S1, int)).xdtor; |
| GC.runFinalizers((cast(char*)dtor2)[0..1]); |
| assert(dtorCount == 3); |
| |
| S1[2][int] aa3; |
| aa3[0] = [S1(0),S1(2)]; |
| aa3[1] = [S1(1),S1(3)]; |
| dtorCount = 0; |
| aa3 = null; |
| auto dtor3 = typeid(TypeInfo_AssociativeArray.Entry!(int, S1[2])).xdtor; |
| GC.runFinalizers((cast(char*)dtor3)[0..1]); |
| assert(dtorCount == 4); |
| } |
| |
| // test struct dtor handling not causing false pointers |
| unittest |
| { |
| // for 64-bit, allocate a struct of size 40 |
| static struct S |
| { |
| size_t[4] data; |
| S* ptr4; |
| } |
| auto p1 = new S; |
| auto p2 = new S; |
| p2.ptr4 = p1; |
| |
| // a struct with a dtor with size 32, but the dtor will cause |
| // allocation to be larger by a pointer |
| static struct A |
| { |
| size_t[3] data; |
| S* ptr3; |
| |
| ~this() {} |
| } |
| |
| GC.free(p2); |
| auto a = new A; // reuse same memory |
| if (cast(void*)a is cast(void*)p2) // reusage not guaranteed |
| { |
| auto ptr = cast(S**)(a + 1); |
| assert(*ptr != p1); // still same data as p2.ptr4? |
| } |
| |
| // small array |
| static struct SArr |
| { |
| void*[10] data; |
| } |
| auto arr1 = new SArr; |
| arr1.data[] = p1; |
| GC.free(arr1); |
| |
| // allocates 2*A.sizeof + (void*).sizeof (TypeInfo) + 1 (array length) |
| auto arr2 = new A[2]; |
| if (cast(void*)arr1 is cast(void*)arr2.ptr) // reusage not guaranteed |
| { |
| auto ptr = cast(S**)(arr2.ptr + 2); |
| assert(*ptr != p1); // still same data as p2.ptr4? |
| } |
| |
| // large array |
| static struct LArr |
| { |
| void*[1023] data; |
| } |
| auto larr1 = new LArr; |
| larr1.data[] = p1; |
| GC.free(larr1); |
| |
| auto larr2 = new S[255]; |
| import core.internal.gc.blockmeta : LARGEPREFIX; |
| if (cast(void*)larr1 is cast(void*)larr2.ptr - LARGEPREFIX) // reusage not guaranteed |
| { |
| auto ptr = cast(S**)larr1; |
| assert(ptr[0] != p1); // 16 bytes array header |
| assert(ptr[1] != p1); |
| version (D_LP64) {} else |
| { |
| assert(ptr[2] != p1); |
| assert(ptr[3] != p1); |
| } |
| } |
| } |
| |
| // 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 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; |
| } |
| } |