/** * Part of the D programming language runtime library. */ /* * Copyright (C) 2004-2008 by Digital Mars, www.digitalmars.com * Written by Walter Bright * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * o The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * o Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * o This notice may not be removed or altered from any source * distribution. */ // Storage allocation module std.gc; //debug = PRINTF; public import std.c.stdarg; public import std.c.stdlib; public import std.c.string; public import gcx; public import std.outofmemory; public import gcstats; public import std.thread; version=GCCLASS; version (GCCLASS) alias GC gc_t; else alias GC* gc_t; gc_t _gc; void addRoot(void *p) { _gc.addRoot(p); } void removeRoot(void *p) { _gc.removeRoot(p); } void addRange(void *pbot, void *ptop) { _gc.addRange(pbot, ptop); } void removeRange(void *pbot) { _gc.removeRange(pbot); } void fullCollect() { _gc.fullCollect(); } void fullCollectNoStack() { _gc.fullCollectNoStack(); } void genCollect() { _gc.genCollect(); } void minimize() { _gc.minimize(); } void disable() { _gc.disable(); } void enable() { _gc.enable(); } void getStats(out GCStats stats) { _gc.getStats(stats); } void hasPointers(void* p) { _gc.hasPointers(p); } void hasNoPointers(void* p) { _gc.hasNoPointers(p); } void setV1_0() { _gc.setV1_0(); } void[] malloc(size_t nbytes) { void* p = _gc.malloc(nbytes); return p[0 .. nbytes]; } void[] realloc(void* p, size_t nbytes) { void* q = _gc.realloc(p, nbytes); return q[0 .. nbytes]; } size_t extend(void* p, size_t minbytes, size_t maxbytes) { return _gc.extend(p, minbytes, maxbytes); } size_t capacity(void* p) { return _gc.capacity(p); } void setTypeInfo(TypeInfo ti, void* p) { if (ti.flags() & 1) hasNoPointers(p); else hasPointers(p); } void* getGCHandle() { return cast(void*)_gc; } void setGCHandle(void* p) { void* oldp = getGCHandle(); gc_t g = cast(gc_t)p; if (g.gcversion != gcx.GCVERSION) throw new Error("incompatible gc versions"); // Add our static data to the new gc GC.scanStaticData(g); _gc = g; // return oldp; } void endGCHandle() { GC.unscanStaticData(_gc); } extern (C) { void _d_monitorrelease(Object h); void gc_init() { version (GCCLASS) { void* p; ClassInfo ci = GC.classinfo; p = std.c.stdlib.malloc(ci.init.length); (cast(byte*)p)[0 .. ci.init.length] = ci.init[]; _gc = cast(GC)p; } else { _gc = cast(GC *) std.c.stdlib.calloc(1, GC.sizeof); } _gc.initialize(); GC.scanStaticData(_gc); std.thread.Thread.thread_init(); } void gc_term() { _gc.fullCollectNoStack(); _gc.Dtor(); } Object _d_newclass(ClassInfo ci) { void *p; debug(PRINTF) printf("_d_newclass(ci = %p, %s)\n", ci, cast(char *)ci.name); if (ci.flags & 1) // if COM object { /* 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 = std.c.stdlib.malloc(ci.init.length); if (!p) _d_OutOfMemory(); debug(PRINTF) printf(" COM object p = %p\n", p); } else { p = _gc.malloc(ci.init.length); debug(PRINTF) printf(" p = %p\n", p); _gc.setFinalizer(p, &new_finalizer); if (ci.flags & 2) _gc.hasNoPointers(p); } debug (PRINTF) { printf("p = %p\n", p); printf("ci = %p, ci.init = %p, len = %d\n", ci, ci.init, ci.init.length); printf("vptr = %p\n", *cast(void **)ci.init); printf("vtbl[0] = %p\n", (*cast(void ***)ci.init)[0]); printf("vtbl[1] = %p\n", (*cast(void ***)ci.init)[1]); printf("init[0] = %x\n", (cast(uint *)ci.init)[0]); printf("init[1] = %x\n", (cast(uint *)ci.init)[1]); printf("init[2] = %x\n", (cast(uint *)ci.init)[2]); printf("init[3] = %x\n", (cast(uint *)ci.init)[3]); printf("init[4] = %x\n", (cast(uint *)ci.init)[4]); } // Initialize it (cast(byte*)p)[0 .. ci.init.length] = ci.init[]; //printf("initialization done\n"); return cast(Object)p; } extern (D) alias void (*fp_t)(Object); // generic function pointer void _d_delinterface(void** p) { if (*p) { Interface *pi = **cast(Interface ***)*p; Object o; o = cast(Object)(*p - pi.offset); _d_delclass(&o); *p = null; } } void _d_delclass(Object *p) { if (*p) { debug (PRINTF) printf("_d_delclass(%p)\n", *p); version(0) { ClassInfo **pc = cast(ClassInfo **)*p; if (*pc) { ClassInfo c = **pc; if (c.deallocator) { _d_callfinalizer(cast(void *)(*p)); fp_t fp = cast(fp_t)c.deallocator; (*fp)(*p); // call deallocator *p = null; return; } } } _gc.free(cast(void*)(*p)); *p = null; } } /****************************************** * 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 */ ulong _d_newarrayT(TypeInfo ti, size_t length) { void *p; ulong result; auto size = ti.next.tsize(); // array element size debug(PRINTF) printf("_d_newarrayT(length = x%x, size = %d)\n", length, size); if (length == 0 || size == 0) result = 0; else { version (D_InlineAsm_X86) { asm { mov EAX,size ; mul EAX,length ; mov size,EAX ; jc Loverflow ; } } else size *= length; p = _gc.malloc(size + 1); debug(PRINTF) printf(" p = %p\n", p); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(p); memset(p, 0, size); result = cast(ulong)length + (cast(ulong)cast(uint)p << 32); } return result; Loverflow: _d_OutOfMemory(); } /* For when the array has a non-zero initializer. */ ulong _d_newarrayiT(TypeInfo ti, size_t length) { ulong result; auto size = ti.next.tsize(); // array element size debug(PRINTF) printf("_d_newarrayiT(length = %d, size = %d)\n", length, size); if (length == 0 || size == 0) result = 0; else { auto initializer = ti.next.init(); auto isize = initializer.length; auto q = initializer.ptr; version (D_InlineAsm_X86) { asm { mov EAX,size ; mul EAX,length ; mov size,EAX ; jc Loverflow ; } } else size *= length; auto p = _gc.malloc(size + 1); debug(PRINTF) printf(" p = %p, isize = %d\n", p, isize); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(p); if (isize == 1) memset(p, *cast(ubyte*)q, size); else if (isize == int.sizeof) { int init = *cast(int*)q; size /= int.sizeof; for (size_t u = 0; u < size; u++) { (cast(int*)p)[u] = init; } } else { for (size_t u = 0; u < size; u += isize) { memcpy(p + u, q, isize); } } va_end(q); result = cast(ulong)length + (cast(ulong)cast(uint)p << 32); } return result; Loverflow: _d_OutOfMemory(); } ulong _d_newarraymT(TypeInfo ti, int ndims, ...) { ulong result; //debug(PRINTF) //printf("_d_newarraymT(ndims = %d)\n", ndims); if (ndims == 0) result = 0; else { va_list q; va_start!(int)(q, ndims); void[] foo(TypeInfo ti, size_t* pdim, int ndims) { size_t dim = *pdim; void[] p; //printf("foo(ti = %p, ti.next = %p, dim = %d, ndims = %d\n", ti, ti.next, dim, ndims); if (ndims == 1) { auto r = _d_newarrayT(ti, dim); p = *cast(void[]*)(&r); } else { p = _gc.malloc(dim * (void[]).sizeof + 1)[0 .. dim]; for (int i = 0; i < dim; i++) { (cast(void[]*)p.ptr)[i] = foo(ti.next, pdim + 1, ndims - 1); } } return p; } size_t* pdim = cast(size_t *)q; result = cast(ulong)foo(ti, pdim, ndims); //printf("result = %llx\n", result); version (none) { for (int i = 0; i < ndims; i++) { printf("index %d: %d\n", i, va_arg!(int)(q)); } } va_end(q); } return result; } ulong _d_newarraymiT(TypeInfo ti, int ndims, ...) { ulong result; //debug(PRINTF) //printf("_d_newarraymi(size = %d, ndims = %d)\n", size, ndims); if (ndims == 0) result = 0; else { va_list q; va_start!(int)(q, ndims); void[] foo(TypeInfo ti, size_t* pdim, int ndims) { size_t dim = *pdim; void[] p; if (ndims == 1) { auto r = _d_newarrayiT(ti, dim); p = *cast(void[]*)(&r); } else { p = _gc.malloc(dim * (void[]).sizeof + 1)[0 .. dim]; for (int i = 0; i < dim; i++) { (cast(void[]*)p.ptr)[i] = foo(ti.next, pdim + 1, ndims - 1); } } return p; } size_t* pdim = cast(size_t *)q; result = cast(ulong)foo(ti, pdim, ndims); //printf("result = %llx\n", result); version (none) { for (int i = 0; i < ndims; i++) { printf("index %d: %d\n", i, va_arg!(int)(q)); printf("init = %d\n", va_arg!(int)(q)); } } va_end(q); } return result; } struct Array { size_t length; byte *data; }; void* _d_allocmemory(size_t nbytes) { return _gc.malloc(nbytes); } /* This function is obsoleted; replaced by _d_delarray_t() */ void _d_delarray(Array *p) { if (p) { assert(!p.length || p.data); if (p.data) _gc.free(p.data); p.data = null; p.length = 0; } } /* Delete an array; ti is the element type. * Should we zero out the array when done? */ void _d_delarray_t(Array *p, TypeInfo ti) { if (p) { assert(!p.length || p.data); if (p.data) { if (ti) { // Call destructors on all the sub-objects auto sz = ti.tsize(); auto pe = p.data; auto pend = pe + p.length * sz; while (pe != pend) { pend -= sz; ti.destroy(pend); } } _gc.free(p.data); } p.data = null; p.length = 0; } } void _d_delmemory(void* *p) { if (*p) { _gc.free(*p); *p = null; } } } void new_finalizer(void *p, bool dummy) { //printf("new_finalizer(p = %p)\n", p); _d_callfinalizer(p); } extern (C) void _d_callinterfacefinalizer(void *p) { //printf("_d_callinterfacefinalizer(p = %p)\n", p); if (p) { Interface *pi = **cast(Interface ***)p; Object o = cast(Object)(p - pi.offset); _d_callfinalizer(cast(void*)o); } } extern (C) void _d_callfinalizer(void *p) { //printf("_d_callfinalizer(p = %p)\n", p); if (p) // not necessary if called from gc { ClassInfo **pc = cast(ClassInfo **)p; if (*pc) { ClassInfo c = **pc; try { do { if (c.destructor) { fp_t fp = cast(fp_t)c.destructor; (*fp)(cast(Object)p); // call destructor } c = c.base; } while (c); if ((cast(void**)p)[1]) // if monitor is not null _d_monitorrelease(cast(Object)p); } finally { *pc = null; // zero vptr } } } } /+ ------------------------------------------------ +/ /****************************** * Resize dynamic arrays with 0 initializers. */ extern (C) byte[] _d_arraysetlengthT(TypeInfo ti, size_t newlength, Array *p) in { assert(ti); assert(!p.length || p.data); } body { byte* newdata; size_t sizeelem = ti.next.tsize(); debug(PRINTF) { printf("_d_arraysetlengthT(p = %p, sizeelem = %d, newlength = %d)\n", p, sizeelem, newlength); if (p) printf("\tp.data = %p, p.length = %d\n", p.data, 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 { size_t newsize = sizeelem * newlength; if (newsize / newlength != sizeelem) goto Loverflow; } //printf("newsize = %x, newlength = %x\n", newsize, newlength); if (p.data) { newdata = p.data; if (newlength > p.length) { size_t size = p.length * sizeelem; size_t cap = _gc.capacity(p.data); if (cap <= newsize) { if (cap >= 4096) { // Try to extend in-place auto u = _gc.extend(p.data, (newsize + 1) - cap, (newsize + 1) - cap); if (u) { goto L1; } } newdata = cast(byte *)_gc.malloc(newsize + 1); newdata[0 .. size] = p.data[0 .. size]; if (!(ti.next.flags() & 1)) _gc.hasNoPointers(newdata); } L1: newdata[size .. newsize] = 0; } } else { newdata = cast(byte *)_gc.calloc(newsize + 1, 1); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(newdata); } } else { newdata = p.data; } p.data = newdata; p.length = newlength; return newdata[0 .. newlength]; Loverflow: _d_OutOfMemory(); } /** * 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) byte[] _d_arraysetlengthiT(TypeInfo ti, size_t newlength, Array *p) in { assert(!p.length || p.data); } body { byte* newdata; size_t sizeelem = ti.next.tsize(); void[] initializer = ti.next.init(); size_t 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.data, 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 { size_t newsize = sizeelem * newlength; if (newsize / newlength != sizeelem) goto Loverflow; } //printf("newsize = %x, newlength = %x\n", newsize, newlength); size_t size = p.length * sizeelem; if (p.data) { newdata = p.data; if (newlength > p.length) { size_t cap = _gc.capacity(p.data); if (cap <= newsize) { if (cap >= 4096) { // Try to extend in-place auto u = _gc.extend(p.data, (newsize + 1) - cap, (newsize + 1) - cap); if (u) { goto L1; } } newdata = cast(byte *)_gc.malloc(newsize + 1); newdata[0 .. size] = p.data[0 .. size]; L1: ; } } } else { newdata = cast(byte *)_gc.malloc(newsize + 1); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(newdata); } auto q = initializer.ptr; // pointer to initializer if (newsize > size) { if (initsize == 1) { //printf("newdata = %p, size = %d, newsize = %d, *q = %d\n", newdata, size, newsize, *cast(byte*)q); newdata[size .. newsize] = *(cast(byte*)q); } else { for (size_t u = size; u < newsize; u += initsize) { memcpy(newdata + u, q, initsize); } } } } else { newdata = p.data; } p.data = newdata; p.length = newlength; return newdata[0 .. newlength]; Loverflow: _d_OutOfMemory(); } /**************************************** * Append y[] to array x[]. * size is size of each array element. */ extern (C) long _d_arrayappendT(TypeInfo ti, Array *px, byte[] y) { auto sizeelem = ti.next.tsize(); // array element size auto cap = _gc.capacity(px.data); auto length = px.length; auto newlength = length + y.length; auto newsize = newlength * sizeelem; if (newsize > cap) { byte* newdata; if (cap >= 4096) { // Try to extend in-place auto u = _gc.extend(px.data, (newsize + 1) - cap, (newsize + 1) - cap); if (u) { goto L1; } } newdata = cast(byte *)_gc.malloc(newCapacity(newlength, sizeelem) + 1); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(newdata); memcpy(newdata, px.data, length * sizeelem); px.data = newdata; } L1: px.length = newlength; memcpy(px.data + length * sizeelem, y.ptr, y.length * sizeelem); return *cast(long*)px; } 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 4096 bytes are left as-is, so for the most * common cases, memory allocation is 1 to 1. The small overhead added * doesn't effect 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 > 4096) { //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); // testing shows 1.02 for large arrays is about the point of diminishing return if (mult < 102) mult = 102; newext = cast(size_t)((newcap * mult) / 100); newext -= newext % size; //printf("mult: %2.2f, mult2: %2.2f, alloc: %2.2f\n",mult/100.0,mult2,newext / cast(double)size); } newcap = newext > newcap ? newext : newcap; //printf("newcap = %d, newlength = %d, size = %d\n", newcap, newlength, size); } return newcap; } extern (C) byte[] _d_arrayappendcT(TypeInfo ti, inout byte[] x, ...) { auto sizeelem = ti.next.tsize(); // array element size auto cap = _gc.capacity(x.ptr); auto length = x.length; auto newlength = length + 1; auto newsize = newlength * sizeelem; assert(cap == 0 || length * sizeelem <= cap); //printf("_d_arrayappendc(sizeelem = %d, ptr = %p, length = %d, cap = %d)\n", sizeelem, x.ptr, x.length, cap); if (newsize >= cap) { byte* newdata; if (cap >= 4096) { // Try to extend in-place auto u = _gc.extend(x.ptr, (newsize + 1) - cap, (newsize + 1) - cap); if (u) { goto L1; } } //printf("_d_arrayappendc(sizeelem = %d, newlength = %d, cap = %d)\n", sizeelem, newlength, cap); cap = newCapacity(newlength, sizeelem); assert(cap >= newlength * sizeelem); newdata = cast(byte *)_gc.malloc(cap + 1); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(newdata); memcpy(newdata, x.ptr, length * sizeelem); (cast(void **)(&x))[1] = newdata; } L1: byte *argp = cast(byte *)(&ti + 2); *cast(size_t *)&x = newlength; x.ptr[length * sizeelem .. newsize] = argp[0 .. sizeelem]; assert((cast(size_t)x.ptr & 15) == 0); assert(_gc.capacity(x.ptr) > x.length * sizeelem); return x; } extern (C) byte[] _d_arraycatT(TypeInfo ti, byte[] x, byte[] y) out (result) { auto sizeelem = ti.next.tsize(); // array element size //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); 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.capacity(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; } //printf("_d_arraycatT(%d,%p ~ %d,%p)\n", x.length, x.ptr, y.length, y.ptr); auto sizeelem = ti.next.tsize(); // array element size //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; byte* p = cast(byte*)_gc.malloc(len + 1); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(p); memcpy(p, x.ptr, xlen); memcpy(p + xlen, y.ptr, ylen); p[len] = 0; return p[0 .. x.length + y.length]; } extern (C) byte[] _d_arraycatnT(TypeInfo ti, uint n, ...) { void* a; size_t length; byte[]* p; uint i; byte[] b; auto sizeelem = ti.next.tsize(); // array element size p = cast(byte[]*)(&n + 1); for (i = 0; i < n; i++) { b = *p++; length += b.length; } if (!length) return null; a = _gc.malloc(length * sizeelem); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(a); p = cast(byte[]*)(&n + 1); uint j = 0; for (i = 0; i < n; i++) { b = *p++; if (b.length) { memcpy(a + j, b.ptr, b.length * sizeelem); j += b.length * sizeelem; } } byte[] result; *cast(int *)&result = length; // jam length (cast(void **)&result)[1] = a; // jam ptr return result; } extern (C) void* _d_arrayliteralT(TypeInfo ti, size_t length, ...) { auto sizeelem = ti.next.tsize(); // array element size void* result; //printf("_d_arrayliteralT(sizeelem = %d, length = %d)\n", sizeelem, length); if (length == 0 || sizeelem == 0) result = null; else { result = _gc.malloc(length * sizeelem); if (!(ti.next.flags() & 1)) { _gc.hasNoPointers(result); } va_list q; va_start!(size_t)(q, length); size_t stacksize = (sizeelem + int.sizeof - 1) & ~(int.sizeof - 1); if (stacksize == sizeelem) { memcpy(result, q, length * sizeelem); } else { for (size_t i = 0; i < length; i++) { memcpy(result + i * sizeelem, q, sizeelem); q += stacksize; } } va_end(q); } return result; } /********************************** * Support for array.dup property. */ struct Array2 { size_t length; void* ptr; } extern (C) long _adDupT(TypeInfo ti, Array2 a) out (result) { auto sizeelem = ti.next.tsize(); // array element size assert(memcmp((*cast(Array2*)&result).ptr, a.ptr, a.length * sizeelem) == 0); } body { Array2 r; if (a.length) { auto sizeelem = ti.next.tsize(); // array element size auto size = a.length * sizeelem; r.ptr = _gc.malloc(size); if (!(ti.next.flags() & 1)) _gc.hasNoPointers(r.ptr); r.length = a.length; memcpy(r.ptr, a.ptr, size); } return *cast(long*)(&r); } unittest { int[] a; int[] b; int i; debug(adi) printf("array.dup.unittest\n"); 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); }