Files
ldc/dmd2/root/dmgcmem.c
2012-04-05 11:12:22 +04:00

500 lines
9.9 KiB
C

// Copyright (c) 2000-2011 by Digital Mars
// All Rights Reserved
// written by Walter Bright
// http://www.digitalmars.com
// License for redistribution is by either the Artistic License
// in artistic.txt, or the GNU General Public License in gnu.txt.
// See the included readme.txt for details.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#if linux || __APPLE__ || __FreeBSD__ || __OpenBSD__
#include <unistd.h>
#include <pthread.h>
#endif
#include "rmem.h"
#include "gc/gc.h"
//#include "printf.h"
/* This implementation of the storage allocator uses the Digital Mars gc.
*/
Mem mem;
//static int nuncollectable;
extern "C"
{
void gc_init();
GC *gc_get();
}
void Mem::init()
{
gc_init();
}
char *Mem::strdup(const char *s)
{
return gc_get()->strdup(s);
}
void *Mem::malloc(size_t size)
{
if (gc) // if cached allocator
{
// PRINTF("Using cached gc for size %d, file = '%s', line = %d\n", size, GC::file, GC::line);
// GC::file = NULL;
// GC::line = 0;
return ((GC *)gc)->malloc(size);
}
if (this == &mem) // don't cache global mem
{
// PRINTF("Using global gc for size %d, file = '%s', line = %d\n", size, GC::file, GC::line);
// GC::file = NULL;
// GC::line = 0;
return gc_get()->malloc(size);
}
// PRINTF("Generating cached gc for size %d, file = '%s', line = %d\n", size, GC::file, GC::line);
gc = gc_get();
return gc->malloc(size);
}
void *Mem::malloc_uncollectable(size_t size)
{ void *p;
p = ::malloc(size);
if (!p)
error();
addroots((char *)p, (char *)p + size);
#if 0
++nuncollectable;
WPRINTF(L"malloc_uncollectable(%u) = %x, n=%d\n", size, p, nuncollectable);
#endif
return p;
}
void *Mem::calloc(size_t size, size_t n)
{
return gc_get()->calloc(size, n);
}
void *Mem::realloc(void *p, size_t size)
{
return gc_get()->realloc(p, size);
}
void Mem::free(void *p)
{
gc_get()->free(p);
}
void Mem::free_uncollectable(void *p)
{
if (p)
{ removeroots((char *)p);
::free(p);
#if 0
--nuncollectable;
WPRINTF(L"free_uncollectable(%x) n=%d\n", p, nuncollectable);
#endif
#if 0
gc_get()->fullcollect();
GCStats stats;
getStats(&stats);
WPRINTF(L"poolsize = %x, usedsize = %x, freelistsize = %x\n",
stats.poolsize, stats.usedsize, stats.freelistsize);
#endif
}
}
void *Mem::mallocdup(void *o, size_t size)
{
return gc_get()->mallocdup(o, size);
}
void Mem::check(void *p)
{
if (gc)
gc->check(p);
else
gc_get()->check(p);
}
void Mem::error()
{
#if linux || __APPLE__ || __FreeBSD__ || __OpenBSD__
assert(0);
#endif
printf("Error: out of memory\n");
exit(EXIT_FAILURE);
}
void Mem::fullcollect()
{
gc_get()->fullcollect();
#if 0
{
GCStats stats;
gc_get()->getStats(&stats);
WPRINTF(L"Thread %x ", Thread::getId());
WPRINTF(L"poolsize=x%x, usedsize=x%x, freelistsize=x%x, freeblocks=%d, pageblocks=%d\n",
stats.poolsize, stats.usedsize, stats.freelistsize, stats.freeblocks, stats.pageblocks);
}
#endif
}
void Mem::fullcollectNoStack()
{
gc_get()->fullcollectNoStack();
#if 0
{
GCStats stats;
gc_get()->getStats(&stats);
WPRINTF(L"Thread %x ", Thread::getId());
WPRINTF(L"poolsize=x%x, usedsize=x%x, freelistsize=x%x, freeblocks=%d, pageblocks=%d\n",
stats.poolsize, stats.usedsize, stats.freelistsize, stats.freeblocks, stats.pageblocks);
}
#endif
}
void Mem::mark(void *pointer)
{
(void) pointer; // for VC /W4 compatibility
}
void Mem::addroots(char* pStart, char* pEnd)
{
gc_get()->addRange(pStart, pEnd);
}
void Mem::removeroots(char* pStart)
{
gc_get()->removeRange(pStart);
}
void Mem::setFinalizer(void* pObj, FINALIZERPROC pFn, void* pClientData)
{
(void)pClientData;
gc_get()->setFinalizer(pObj, pFn);
}
void Mem::setStackBottom(void *stackbottom)
{
gc_get()->setStackBottom(stackbottom);
}
GC *Mem::getThreadGC()
{
return gc_get();
}
/* =================================================== */
#if 1
void * operator new(size_t m_size)
{
//PRINTF("Call to global operator new(%d), file = '%s', line = %d\n", m_size, GC::file ? GC::file : "(null)", GC::line);
GC::file = NULL;
GC::line = 0;
return mem.malloc(m_size);
}
void operator delete(void *p)
{
//WPRINTF(L"Call to global operator delete\n");
mem.free(p);
}
void* operator new[](size_t size)
{
return operator new(size);
}
void operator delete[](void *pv)
{
operator delete(pv);
}
#endif
void * Mem::operator new(size_t m_size)
{ void *p;
p = gc_get()->malloc(m_size);
//printf("Mem::operator new(%d) = %p\n", m_size, p);
if (!p)
mem.error();
return p;
}
void * Mem::operator new(size_t m_size, Mem *mem)
{ void *p;
p = mem->malloc(m_size);
//printf("Mem::operator new(%d) = %p\n", m_size, p);
if (!p)
::mem.error();
return p;
}
void * Mem::operator new(size_t m_size, GC *gc)
{ void *p;
// if (!gc)
// WPRINTF(L"gc is NULL\n");
p = gc->malloc(m_size);
//printf("Mem::operator new(%d) = %p\n", m_size, p);
if (!p)
::mem.error();
return p;
}
void Mem::operator delete(void *p)
{
// printf("Mem::operator delete(%p)\n", p);
gc_get()->free(p);
}
/* ============================================================ */
/* The following section of code exists to find the right
* garbage collector for this thread. There is one independent instance
* of the collector per thread.
*/
/* ===================== linux ================================ */
#if linux || __APPLE__ || __FreeBSD__ || __OpenBSD__
#include <pthread.h>
#define LOG 0 // log thread creation / destruction
extern "C"
{
// Key identifying the thread-specific data
static pthread_key_t gc_key;
/* "Once" variable ensuring that the key for gc_alloc will be allocated
* exactly once.
*/
static pthread_once_t gc_alloc_key_once = PTHREAD_ONCE_INIT;
/* Forward functions */
static void gc_alloc_key();
static void gc_alloc_destroy_gc(void * accu);
void gc_init()
{
#if LOG
WPRINTF(L"Thread %lx: gc_init()\n", pthread_self());
#endif
pthread_once(&gc_alloc_key_once, gc_alloc_key);
#if LOG
WPRINTF(L"Thread %lx: gc_init() return\n", pthread_self());
#endif
}
GC *gc_get()
{
GC *gc;
// Get the thread-specific data associated with the key
gc = (GC *) pthread_getspecific(gc_key);
// It's initially NULL, meaning that we must allocate the buffer first.
if (gc == NULL)
{
GC_LOG();
gc = new GC();
gc->init();
// Store the buffer pointer in the thread-specific data.
pthread_setspecific(gc_key, (void *) gc);
#if LOG
WPRINTF(L"Thread %lx: allocating gc at %x\n", pthread_self(), gc);
#endif
}
return gc;
}
// Function to allocate the key for gc_alloc thread-specific data.
static void gc_alloc_key()
{
pthread_key_create(&gc_key, gc_alloc_destroy_gc);
#if LOG
WPRINTF(L"Thread %lx: allocated gc key %d\n", pthread_self(), gc_key);
#endif
}
// Function to free the buffer when the thread exits.
// Called only when the thread-specific data is not NULL.
static void gc_alloc_destroy_gc(void *gc)
{
#if LOG
WPRINTF(L"Thread %x: freeing gc at %x\n", pthread_self(), gc);
#endif
delete (GC *)gc;
}
}
#endif
/* ===================== win32 ================================ */
#if !defined(linux) && defined(_WIN32)
#if 1 // single threaded version
extern "C"
{
static GC *gc;
void gc_init()
{
if (!gc)
{ gc = (GC *)::malloc(sizeof(GC));
gc->init();
}
}
GC *gc_get()
{
return gc;
}
}
#else // multi threaded version
#include "mutex.h"
#include "thread.h"
/* This is the win32 version. It suffers from the bug that
* when the thread exits the data structure is not cleared,
* but the memory pool it points to is free'd.
* Thus, if a new thread comes along with the same thread id,
* the data will look initialized, but will point to garbage.
*
* What needs to happen is when a thread exits, the associated
* GC_context data struct is cleared.
*/
struct GC_context
{
ThreadId threadid; // identifier of current thread
GC *gc;
};
Mutex gc_mutex;
static GC_context array[64];
// Array of pointers to GC_context objects, one per threadid
GC_context *gccontext = array;
unsigned gccontext_allocdim = 64;
unsigned gccontext_dim;
ThreadId gc_cache_ti;
GC_context *gc_cache_cc;
extern "C" void gc_init()
{
}
extern "C" GC *gc_get()
{
/* This works by creating an array of GC_context's, one
* for each thread. We match up by thread id.
*/
ThreadId ti;
GC_context *cc;
//PRINTF("gc_get()\n");
ti = Thread::getId();
gc_mutex.acquire();
// Used cached version if we can
if (ti == gc_cache_ti)
{
cc = gc_cache_cc;
//exception(L"getGC_context(): cache x%x", ti);
}
else
{
// This does a linear search through gccontext[].
// A hash table might be faster if there are more
// than a dozen threads.
GC_context *ccp;
GC_context *ccptop = &gccontext[gccontext_dim];
for (ccp = gccontext; ccp < ccptop; ccp++)
{
cc = ccp;
if (cc->threadid == ti)
{
WPRINTF(L"getGC_context(): existing x%x", ti);
goto Lret;
}
}
// Do not allocate with garbage collector, as this must reside
// global to all threads.
assert(gccontext_dim < gccontext_allocdim);
cc = ccp;
memset(cc, 0, sizeof(*cc));
cc->threadid = ti;
cc->gc = new GC();
cc->gc->init();
gccontext_dim++;
WPRINTF(L"getGC_context(): new x%x\n", ti);
Lret:
// Cache for next time
gc_cache_ti = ti;
gc_cache_cc = cc;
}
gc_mutex.release();
return cc->gc;
}
#endif
#endif