mirror of
https://github.com/darlinghq/darling-libobjc2.git
synced 2025-01-01 09:18:29 +00:00
231 lines
5.8 KiB
Objective-C
231 lines
5.8 KiB
Objective-C
#include "../objc/runtime.h"
|
|
BOOL forceCollect;
|
|
#import "object.h"
|
|
#import "thread.h"
|
|
#import "cycle.h"
|
|
#import "trace.h"
|
|
#import "workqueue.h"
|
|
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
/**
|
|
* Size of the buffers used in each thread before passing stuff over to the GC
|
|
* thread. Once either BUFFER_SIZE objects are queued waiting for tracing or
|
|
* cycle detection, the queue is passed over to the GC thread, which wakes up
|
|
* and tries to find some things to delete.
|
|
*/
|
|
static const int BUFFER_SIZE = 256;
|
|
|
|
|
|
/**
|
|
* Thread local storage key used for the thread structure.
|
|
*/
|
|
static pthread_key_t gc_thread_key;
|
|
/**
|
|
*
|
|
*/
|
|
static pthread_mutex_t thread_lock;
|
|
|
|
static void GCDrainThread(GCThread *thread, BOOL forceCollect);
|
|
|
|
GCThread *GCThreadList;
|
|
|
|
/**
|
|
* Removes this thread from the list. Must run in the GC thread.
|
|
*/
|
|
static void GCUnregisterThread(void *t)
|
|
{
|
|
GCThread *thr = t;
|
|
if (NULL == thr->last)
|
|
{
|
|
GCThreadList = thr->next;
|
|
}
|
|
else
|
|
{
|
|
thr->last->next = thr->next;
|
|
}
|
|
if (NULL == thr->next)
|
|
{
|
|
if (thr->last)
|
|
{
|
|
thr->next->last = thr->last;
|
|
}
|
|
else
|
|
{
|
|
thr->next->last = GCThreadList;
|
|
}
|
|
}
|
|
//FIXME: Delete tracer references to this stack
|
|
// Wake up the caller thread and let it do the real cleanup
|
|
pthread_mutex_lock(&thread_lock);
|
|
pthread_cond_signal(thr->exitCondition);
|
|
}
|
|
|
|
/**
|
|
* Adds the thread to the list. Must run in the GC thread.
|
|
*/
|
|
static void GCAddThread(void *t)
|
|
{
|
|
GCThread *thr = t;
|
|
thr->next = GCThreadList;
|
|
if (GCThreadList)
|
|
{
|
|
GCThreadList->last = thr;
|
|
}
|
|
GCThreadList = thr;
|
|
}
|
|
|
|
/**
|
|
* Cleanup function called when a thread is destroyed. Pops all autorelease
|
|
* pools, deletes all unaliased objects, and so on.
|
|
*/
|
|
static void cleanup_thread(void *thread)
|
|
{
|
|
GCThread *thr = thread;
|
|
GCDrainThread(thread, NO);
|
|
pthread_setspecific(gc_thread_key, NULL);
|
|
pthread_cond_t thread_exit_condition;
|
|
pthread_cond_init(&thread_exit_condition, NULL);
|
|
thr->exitCondition = &thread_exit_condition;
|
|
pthread_mutex_lock(&thread_lock);
|
|
GCPerform(GCUnregisterThread, thread);
|
|
pthread_cond_wait(&thread_exit_condition, &thread_lock);
|
|
pthread_cond_destroy(&thread_exit_condition);
|
|
free(thr->cycleBuffer);
|
|
free(thr->freeBuffer);
|
|
free(thr);
|
|
}
|
|
|
|
/**
|
|
* Thread system initialization.
|
|
*/
|
|
__attribute__((constructor))
|
|
static void init_thread_system(void)
|
|
{
|
|
pthread_key_create(&gc_thread_key, cleanup_thread);
|
|
pthread_mutex_init(&thread_lock, NULL);
|
|
GCRegisterThread();
|
|
}
|
|
|
|
|
|
void GCRegisterThread(void)
|
|
{
|
|
assert(NULL == pthread_getspecific(gc_thread_key) &&
|
|
"Only one thread per thread!");
|
|
GCThread *thr = calloc(sizeof(GCThread),1);
|
|
// Store this in TLS
|
|
pthread_setspecific(gc_thread_key, thr);
|
|
// FIXME: Use non-portable pthread calls to find the stack.
|
|
// This code is, basically, completely wrong.
|
|
char a;
|
|
thr->stackTop = &a;
|
|
while ((intptr_t)thr->stackTop % 4096)
|
|
{
|
|
thr->stackTop = ((char*)thr->stackTop)+1;
|
|
}
|
|
|
|
thr->cycleBuffer = calloc(BUFFER_SIZE, sizeof(void*));
|
|
thr->freeBuffer = calloc(BUFFER_SIZE, sizeof(void*));
|
|
GCPerform(GCAddThread, thr);
|
|
}
|
|
void GCAddObject(id anObject)
|
|
{
|
|
GCThread *thr = pthread_getspecific(gc_thread_key);
|
|
// If the reference count is 0, we add this
|
|
if (GCGetRetainCount(anObject) == 0)
|
|
{
|
|
thr->freeBuffer[thr->freeBufferInsert++] = anObject;
|
|
if (thr->freeBufferInsert == BUFFER_SIZE)
|
|
{
|
|
GCDrainThread(thr, NO);
|
|
}
|
|
}
|
|
else if (!GCTestFlag(anObject, GCFlagBuffered))
|
|
{
|
|
// Note: there is a potential race here. If this occurs then two
|
|
// GCAutoreleasePools might add the same object to their buffers. This
|
|
// is not important. If it does happen then we run the cycle detector
|
|
// on an object twice. This increases the complexity of the collector
|
|
// above linear, but the cost of making sure that it never happens is
|
|
// much greater than the cost of (very) occasionally checking an object
|
|
// twice if it happens to be added at exactly the same time by two
|
|
// threads.
|
|
GCSetFlag(anObject, GCFlagBuffered);
|
|
thr->cycleBuffer[thr->cycleBufferInsert++] = anObject;
|
|
if (thr->cycleBufferInsert == BUFFER_SIZE)
|
|
{
|
|
GCDrainThread(thr, NO);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
id *cycleBuffer;
|
|
unsigned int cycleBufferSize;
|
|
BOOL forceTrace;
|
|
} GCTraceContext;
|
|
|
|
/**
|
|
* Trampoline that is run in the GC thread.
|
|
*/
|
|
static void traceTrampoline(void *c)
|
|
{
|
|
GCTraceContext *context = c;
|
|
|
|
// Scan for any new garbage cycles.
|
|
if (context->cycleBufferSize)
|
|
{
|
|
GCScanForCycles(context->cycleBuffer, context->cycleBufferSize);
|
|
free(context->cycleBuffer);
|
|
}
|
|
// Now add the objects that might be garbage to the collector.
|
|
// These won't actually be freed until after this
|
|
GCRunTracerIfNeeded(context->forceTrace);
|
|
|
|
//free(c);
|
|
}
|
|
|
|
/**
|
|
* Collect garbage cycles.
|
|
*/
|
|
static void GCDrainThread(GCThread *thread, BOOL forceCollect)
|
|
{
|
|
// Register these objects for tracing
|
|
GCAddObjectsForTracing(thread);
|
|
// Tweak the bottom of the stack to be in this stack frame. Anything in
|
|
// the caller will be traced, but anything in the callee will be ignored
|
|
// (this is important because otherwise you'd find objects because you were
|
|
// looking for them)
|
|
void *stackBottom = &stackBottom;
|
|
thread->stackBottom = stackBottom;
|
|
// Mark all objects on this thread's stack as visited.
|
|
GCTraceStackSynchronous(thread);
|
|
|
|
GCTraceContext *context = calloc(sizeof(GCTraceContext), 1);
|
|
void *
|
|
valloc(size_t size);
|
|
//GCTraceContext *context = valloc(4096);
|
|
if (thread->cycleBufferInsert)
|
|
{
|
|
context->cycleBuffer = thread->cycleBuffer;
|
|
context->cycleBufferSize = thread->cycleBufferInsert;
|
|
thread->cycleBuffer = calloc(BUFFER_SIZE, sizeof(void*));
|
|
thread->cycleBufferInsert = 0;
|
|
}
|
|
context->forceTrace = forceCollect;
|
|
//mprotect(context, 4096, PROT_READ);
|
|
GCPerform(traceTrampoline, context);
|
|
thread->freeBufferInsert = 0;
|
|
}
|
|
void GCDrain(BOOL forceCollect)
|
|
{
|
|
GCThread *thr = pthread_getspecific(gc_thread_key);
|
|
GCDrainThread(thr, forceCollect);
|
|
}
|