Removed GCKit.
parent
3769a48cd7
commit
3b96938128
@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2009 David Chisnall
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
GCKit
|
|
||||||
=====
|
|
||||||
|
|
||||||
GCKit is a garbage collection kit designed for Objective-C. It is a hybrid collector, using a combination of reference counting, cycle detection, and tracing. The design goals are:
|
|
||||||
|
|
||||||
- Easy interoperability with non-GC code using retain/release semantics.
|
|
||||||
- Easy interoperability with code designed for Apple's GC implementation.
|
|
||||||
- Support for assisted reference counting with no compiler support.
|
|
||||||
- Support for automatic garbage collection with compiler support.
|
|
||||||
- Low overheads.
|
|
||||||
- Performance in memory-constrained conditions, without causing undue swapping
|
|
||||||
|
|
||||||
Memory Types
|
|
||||||
------------
|
|
||||||
|
|
||||||
There are three types of memory in GCKit's model:
|
|
||||||
|
|
||||||
- Objects
|
|
||||||
- Traced regions
|
|
||||||
- Untraced regions
|
|
||||||
|
|
||||||
Objects have a fixed layout and may contain strong, weak, and traced pointers.
|
|
||||||
|
|
||||||
Traced regions include the stack, and any regions explicitly designated for
|
|
||||||
tracing. Stacks are traced synchronously, from the thread that owns them,
|
|
||||||
while other regions are not.
|
|
||||||
|
|
||||||
Untraced regions are opaque to GCKit. They may contain pointers to GC'd
|
|
||||||
objects only if the pointers are manually reference counted using GCRetain()
|
|
||||||
and GCRelease().
|
|
||||||
|
|
||||||
Object Types
|
|
||||||
------------
|
|
||||||
|
|
||||||
GCKit will allocate two kind of memory. Objective-C objects, and buffers.
|
|
||||||
|
|
||||||
Reference Types
|
|
||||||
---------------
|
|
||||||
|
|
||||||
There are four kinds of reference (pointer) in GCKit's memory model:
|
|
||||||
|
|
||||||
- Strong.
|
|
||||||
- Zeroing weak.
|
|
||||||
- Traced.
|
|
||||||
- Invisible.
|
|
||||||
|
|
||||||
Strong references use reference counting. When an object is strongly assigned
|
|
||||||
to a pointer, its reference count is incremented and the reference count of the
|
|
||||||
old object is decremented. Objects will never be deleted as long as they have
|
|
||||||
a strong reference count greater than 0 and their references can not all be
|
|
||||||
accounted for by cycles.
|
|
||||||
|
|
||||||
Zeroing weak references are also reference counted, however they do not prevent
|
|
||||||
an object from being finalized. Zeroing weak references follow similar
|
|
||||||
assignment semantics to strong references. When an object is only referenced
|
|
||||||
by zeroing weak references, it will be finalized, but not freed. Subsequent
|
|
||||||
reads of zeroing weak pointers to the object will decrement its reference count
|
|
||||||
and it will be freed once this reaches 0.
|
|
||||||
|
|
||||||
Traced pointers do not perform any reference counting. All pointers on the
|
|
||||||
stack are traced, as are pointers in memory buffers explicitly allocated for
|
|
||||||
tracing. Objects with a reference count of 0 will not be freed until tracing
|
|
||||||
these regions determines that there are no unseen references to them.
|
|
||||||
|
|
||||||
Copying traced pointers between stacks directly is not supported. If a thread
|
|
||||||
somehow gets a reference to another thread's stack and copies a pointer then
|
|
||||||
the compiler will not generate a write barrier. This means that, if the two
|
|
||||||
threads' stacks are not traced in the right order relative to each other (50%
|
|
||||||
chance) and there are no locatable heap references to the object then it may be
|
|
||||||
freed.
|
|
||||||
|
|
||||||
Invisible pointers are outside regions that the garbage collector knows about.
|
|
||||||
Objects pointed to by these may be deleted if their reference count hits 0.
|
|
||||||
|
|
||||||
Interior pointers are not supported. A pointer to the start of an object or
|
|
||||||
managed buffer must be maintained
|
|
||||||
|
|
||||||
Object Deletion
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Objects marked as using CoreFoundation semantics are deleted as soon as their
|
|
||||||
reference counts hit 0.
|
|
||||||
|
|
||||||
All other objects are marked as potential garbage once their reference count
|
|
||||||
drops to a value that is equal to the number of references that the cycle
|
|
||||||
detector can find. If A and B both hold strong references to each other, then
|
|
||||||
they are marked as potential garbage once their reference count hits 1.
|
|
||||||
|
|
||||||
Interfaces
|
|
||||||
----------
|
|
||||||
|
|
||||||
Writing pointers into traced heap memory requires a write barrier. The
|
|
||||||
objc_assign_strongCast function generates this barrier for a single write
|
|
||||||
(another function, as yet unwritten, will generate it for multiple writes).
|
|
||||||
|
|
||||||
Assignments to instance variables or globals must increment the strong
|
|
||||||
reference count of the new value and decrement the value of the old one. The
|
|
||||||
objc_assignIvar() and objc_assignGlobal() functions perform this for you.
|
|
||||||
|
|
||||||
If you are storing pointers in memory that is not managed by GCKit then you
|
|
||||||
must call the CGRetain() function on the pointer to prevent it from being freed
|
|
||||||
and the GCRelease() function when you are finished with it.
|
|
||||||
|
|
||||||
Degenerate Cases
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Objects using the Core Foundation or OpenStep models may set a flag indicating
|
|
||||||
that they do not contain cycles (or, more accurately, that the programmer takes
|
|
||||||
responsibility for freeing cycles). In this case, GCKit will trace the stack,
|
|
||||||
catching bugs where you might have used -release instead of -autorelease, but
|
|
||||||
aside from that will not provide any benefits.
|
|
||||||
|
|
||||||
Objects may also be marked as having CF semantics. In this case, they will be
|
|
||||||
checked for cycles (unless explicitly marked as acyclic), but will be finalised
|
|
||||||
when their reference count hits zero and subsequently destroyed when their weak
|
|
||||||
reference count hits zero.
|
|
||||||
|
|
||||||
Finally, you can use traced memory for everything. Don't do this. GCKit is
|
|
||||||
designed to be efficient when only a relatively small proportion of allocated
|
|
||||||
memory needs to be traced.
|
|
||||||
|
|
||||||
Outstanding Bugs
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Lots. Seriously, don't use GCKit yet. Cycles in traced memory are not yet
|
|
||||||
detected. Lots of GCKit is completely untested.
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
include $(GNUSTEP_MAKEFILES)/common.make
|
|
||||||
|
|
||||||
#LIBRARY_NAME = GCKit
|
|
||||||
TOOL_NAME = GCKit
|
|
||||||
|
|
||||||
GCKit_VERSION = 1
|
|
||||||
|
|
||||||
GCKit_OBJC_FILES = \
|
|
||||||
cycle.m\
|
|
||||||
inline.m\
|
|
||||||
malloc.m\
|
|
||||||
thread.m\
|
|
||||||
trace.m\
|
|
||||||
test.m\
|
|
||||||
visit.m\
|
|
||||||
workqueue.m
|
|
||||||
|
|
||||||
GCKit_HEADER_FILES_INSTALL_DIR = GCKit
|
|
||||||
|
|
||||||
GCKit_HEADER_FILES = \
|
|
||||||
GCKit.h
|
|
||||||
|
|
||||||
GCKit_LIBRARIES_DEPEND_UPON += -lpthread
|
|
||||||
|
|
||||||
GCKit_OBJCFLAGS += -Werror -std=c99 -fno-inline
|
|
||||||
|
|
||||||
include $(GNUSTEP_MAKEFILES)/library.make
|
|
||||||
include $(GNUSTEP_MAKEFILES)/tool.make
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* Category on NSObject to support automatic cycle detection.
|
|
||||||
*/
|
|
||||||
@implementation NSObject (CycleDetection)
|
|
||||||
/**
|
|
||||||
* Increments the 16-bit reference count. Replaces version that sets a
|
|
||||||
* one-word reference count.
|
|
||||||
*/
|
|
||||||
- (id) retain
|
|
||||||
{
|
|
||||||
return GCRetain(self);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Decrements the reference count for an object. If the reference count
|
|
||||||
* reaches zero, calls -dealloc. If the reference count is not zero then the
|
|
||||||
* objectt may be part of a cycle. In this case, it is addded to a buffer and
|
|
||||||
* cycle detection is later invoked.
|
|
||||||
*/
|
|
||||||
- (void) release
|
|
||||||
{
|
|
||||||
GCRelease(self);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Dealloc now does not free objects, they are freed after -dealloc is called.
|
|
||||||
*/
|
|
||||||
- (void) dealloc
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
void GCScanForCycles(id *loopBuffer, unsigned count);
|
|
||||||
id GCRetain(id anObject);
|
|
||||||
void GCRelease(id anObject);
|
|
||||||
@ -1,253 +0,0 @@
|
|||||||
#include "../objc/runtime.h"
|
|
||||||
#import "object.h"
|
|
||||||
#import "malloc.h"
|
|
||||||
#import "thread.h"
|
|
||||||
#import "visit.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
id GCRetain(id anObject)
|
|
||||||
{
|
|
||||||
GCIncrementRetainCount(anObject);
|
|
||||||
GCSetFlag(anObject, GCFlagEscaped);
|
|
||||||
return anObject;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Collect garbage cycles. Inspects every object in the loopBuffer and frees
|
|
||||||
* any that are part of garbage cycles. This is an implementation of the
|
|
||||||
* algorithm described in:
|
|
||||||
*
|
|
||||||
* http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void GCRelease(id anObject)
|
|
||||||
{
|
|
||||||
// If decrementing the strong retain count is 0, the object is probably
|
|
||||||
// garbage. Add it to the list to trace and throw it away if it is.
|
|
||||||
if (GCDecrementRetainCount(anObject) <= 0)
|
|
||||||
{
|
|
||||||
// FIXME: Discard it immediately if it is using CF semantics
|
|
||||||
// Mark this object as in-use or free
|
|
||||||
GCSetColourOfObject(anObject, GCColourBlack);
|
|
||||||
// Clear its buffered flag (we won't look at it again)
|
|
||||||
GCClearFlag(anObject, GCFlagBuffered);
|
|
||||||
// Add it for freeing if tracing doesn't find any references to it
|
|
||||||
GCAddObject(anObject);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If this object is not marked as acyclic
|
|
||||||
if (GCColourOfObject(anObject) == GCColourGreen)
|
|
||||||
{
|
|
||||||
// Mark it as the possible root of a cycle. The object was
|
|
||||||
// released, but there are still strong references to it. That
|
|
||||||
// means that it has
|
|
||||||
GCSetColourOfObject(anObject, GCColourPurple);
|
|
||||||
GCSetFlag(anObject, GCFlagBuffered);
|
|
||||||
GCAddObject(anObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCAddObjectForTracing(id object);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan children turning them black and incrementing the reference count. Used
|
|
||||||
* for objects which have been determined to be acyclic.
|
|
||||||
*/
|
|
||||||
static void GCScanBlackChild(id anObject, void *unused, BOOL isWeak)
|
|
||||||
{
|
|
||||||
GCIncrementRetainCount(anObject);
|
|
||||||
if (GCColourOfObject(anObject) != GCColourBlack)
|
|
||||||
{
|
|
||||||
GCSetColourOfObject(anObject, GCColourBlack);
|
|
||||||
GCVisitChildren(anObject, GCScanBlackChild, NULL, NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan objects turning them black if they are not part of a cycle and white if
|
|
||||||
* they are.
|
|
||||||
*/
|
|
||||||
static void GCScan(id anObject, void* unused, BOOL isWeak)
|
|
||||||
{
|
|
||||||
GCColour colour = GCColourOfObject(anObject);
|
|
||||||
// If the object is not grey, then we've visited it already.
|
|
||||||
if (colour == GCColourGrey)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "%x has retain count of %d\n", (int)anObject, (int)GCGetRetainCount(anObject));
|
|
||||||
// If the retain count is still > 0, we didn't account for all of the
|
|
||||||
// references with cycle detection, so mark it as black and reset the
|
|
||||||
// retain count of every object that it references.
|
|
||||||
//
|
|
||||||
// If it did reach 0, then this is part of a garbage cycle so colour it
|
|
||||||
// accordingly. Any objects reachable from this object do not get
|
|
||||||
// their reference counts restored.
|
|
||||||
//
|
|
||||||
// FIXME: We need to be able to resurrect objects if they are
|
|
||||||
// GCRetain()'d when they are white
|
|
||||||
if (GCGetRetainCount(anObject) > 0)
|
|
||||||
{
|
|
||||||
GCSetColourOfObject(anObject, GCColourBlack);
|
|
||||||
GCVisitChildren(anObject, GCScanBlackChild, NULL, NO);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GCSetColourOfObject(anObject, GCColourWhite);
|
|
||||||
GCVisitChildren(anObject, GCScan, NULL, NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect objects which are coloured white.
|
|
||||||
*
|
|
||||||
* In the original algorithm, white objects were collected immediately. In
|
|
||||||
* this version, it's possible that they have traced pointers referencing them,
|
|
||||||
* so we defer collection. We can only collect a garbage cycle when there are
|
|
||||||
* no traced pointers to any of the nodes.
|
|
||||||
*/
|
|
||||||
static void GCCollectWhite(id anObject, void *ignored, BOOL isWeak)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Looking at object %x with colour %s\n", (unsigned) anObject, [GCStringFromColour(GCColourOfObject(anObject)) UTF8String]);
|
|
||||||
if ((GCColourOfObject(anObject) == GCColourWhite))
|
|
||||||
{
|
|
||||||
GCSetColourOfObject(anObject, GCColourRed);
|
|
||||||
//fprintf(stderr, "%x marked red. Red's dead, baby!\n", (int)anObject);
|
|
||||||
//fprintf(stderr, " has refcount %d!\n", (int)GCGetRetainCount(anObject));
|
|
||||||
GCAddObjectForTracing(anObject);
|
|
||||||
GCVisitChildren(anObject, GCCollectWhite, NULL, NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark objects grey if are not already grey.
|
|
||||||
*
|
|
||||||
* Grey indicates that an object is possibly a member of a cycle. We check
|
|
||||||
* that by traversing all reachable objects from the potential root of a cycle,
|
|
||||||
* decrementing their reference count, and marking them grey. If the reference
|
|
||||||
* count drops to 0, it indicates that all of the strong references to this
|
|
||||||
* object come from cycles.
|
|
||||||
*/
|
|
||||||
void GCMarkGreyChildren(id anObject, void *ignored, BOOL isWeak)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Marking %x as grey\n", (int)anObject);
|
|
||||||
// FIXME: This should probably check if the colour is green. Green objects
|
|
||||||
// can't be parts of cycles, and we need to restore the green colour after
|
|
||||||
// scanning anyway.
|
|
||||||
GCDecrementRetainCount(anObject);
|
|
||||||
if (GCColourOfObject(anObject) != GCColourGrey)
|
|
||||||
{
|
|
||||||
GCSetColourOfObject(anObject, GCColourGrey);
|
|
||||||
GCVisitChildren(anObject, GCMarkGreyChildren, NULL, NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCScanForCycles(id *loopBuffer, unsigned count)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Starting to detect cycles...\n");
|
|
||||||
// Mark Roots
|
|
||||||
id next;
|
|
||||||
for (unsigned i=0 ; i<count ; i++)
|
|
||||||
{
|
|
||||||
next = loopBuffer[i];
|
|
||||||
//fprintf(stderr, "Looking at %x\n", (int)next);
|
|
||||||
// Check that this object is still eligible for cycle detection
|
|
||||||
if (nil == next) continue;
|
|
||||||
if (GCTestFlag(next, GCFlagNotObject)) continue;
|
|
||||||
if (!GCTestFlag(next, GCFlagBuffered))
|
|
||||||
{
|
|
||||||
loopBuffer[i] = nil;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
GCColour colour = GCColourOfObject(next);
|
|
||||||
// If this is the potential root of a cycle (which it might not be
|
|
||||||
// anymore, if something else has changed its colour)
|
|
||||||
if (colour == GCColourPurple)
|
|
||||||
{
|
|
||||||
// Mark it, and all of its children, as grey.
|
|
||||||
//fprintf(stderr, "Marking grey: %d...\n", colour);
|
|
||||||
GCSetColourOfObject(next, GCColourGrey);
|
|
||||||
GCVisitChildren(next, GCMarkGreyChildren, nil, NO);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GCClearFlag(next, GCFlagBuffered);
|
|
||||||
// If the object's refcount is 0, add it to the list to free if the
|
|
||||||
// tracer can't find them.
|
|
||||||
if ((colour == GCColourBlack) && (GCGetRetainCount(next) <= 0))
|
|
||||||
{
|
|
||||||
GCAddObjectForTracing(next);
|
|
||||||
}
|
|
||||||
loopBuffer[i] = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Scan roots
|
|
||||||
for (unsigned i=0 ; i<count ; i++)
|
|
||||||
{
|
|
||||||
next = loopBuffer[i];
|
|
||||||
if (nil == next) continue;
|
|
||||||
//fprintf(stderr, "scanning object...\n");
|
|
||||||
GCScan(next, NULL, NO);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned i=0 ; i<count ; i++)
|
|
||||||
{
|
|
||||||
next = loopBuffer[i];
|
|
||||||
if (nil == next) continue;
|
|
||||||
GCCollectWhite(next, NULL, NO);
|
|
||||||
}
|
|
||||||
void GCRunTracerIfNeeded(BOOL);
|
|
||||||
GCRunTracerIfNeeded(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// Code from the old GCKit for drawing pretty pictures.
|
|
||||||
// FIXME: Make it draw pretty pictures with the new GCKit too.
|
|
||||||
/**
|
|
||||||
* Table of objects that have already been visualised.
|
|
||||||
*/
|
|
||||||
NSHashTable __thread drawnObjects;
|
|
||||||
/**
|
|
||||||
* Recursively output connections from this object in GraphViz .dot format.
|
|
||||||
*/
|
|
||||||
void vizGraph(id self, SEL _cmd, NSString *parent)
|
|
||||||
{
|
|
||||||
NSString *me = [NSString stringWithFormat:@"object%d", (unsigned)self];
|
|
||||||
if (NULL != NSHashGet(drawnObjects, self))
|
|
||||||
{
|
|
||||||
if (nil != parent)
|
|
||||||
{
|
|
||||||
printf("\t%s -> %s\n", [parent UTF8String], [me UTF8String]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Add the node:
|
|
||||||
if (GCColourOfObject(self) == black)
|
|
||||||
{
|
|
||||||
printf("\t%s [style=filled, fillcolor=black, fontcolor=white, label=\"%s\"]\n", [me UTF8String], self->class_pointer->name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("\t%s [style=filled, fillcolor=%s, label=\"%s\"]\n", [me UTF8String], [GCStringFromColour(GCColourOfObject(self)) UTF8String], self->class_pointer->name);
|
|
||||||
}
|
|
||||||
// Add the connection to the parent
|
|
||||||
if (nil != parent)
|
|
||||||
{
|
|
||||||
printf("\t%s -> %s\n", [parent UTF8String], [me UTF8String]);
|
|
||||||
}
|
|
||||||
NSHashInsert(drawnObjects, self);
|
|
||||||
for_all_children(self, (IMP)vizGraph, _cmd, me);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Print a GraphViz-compatible graph of all objects reachable from this one and
|
|
||||||
* their colours.
|
|
||||||
*/
|
|
||||||
void visObject(id object, NSString *graphName)
|
|
||||||
{
|
|
||||||
drawnObjects = NSCreateHashTable(NSNonOwnedPointerHashCallBacks, 100);
|
|
||||||
printf("digraph %s {\n", [graphName UTF8String]);
|
|
||||||
vizGraph(object, @selector(vizGraph:), nil);
|
|
||||||
printf("}\n");
|
|
||||||
NSFreeHashTable(drawnObjects);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* Make sure that all inline functions that are part of the public API are emitted.
|
|
||||||
*/
|
|
||||||
#include "../objc/runtime.h"
|
|
||||||
#define GCINLINEPUBLIC
|
|
||||||
#include "object.h"
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
/**
|
|
||||||
* malloc.h - defines allocation and deallocation hooks and functions for GCKit.
|
|
||||||
*/
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate new memory.
|
|
||||||
*/
|
|
||||||
extern void *(*gc_alloc_with_zone)(void *zone, size_t bytes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Free memory allocated by gc_alloc_with_zone().
|
|
||||||
*/
|
|
||||||
extern void (*gc_free_with_zone)(void *zone, void *mem);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocates an instance of a class, optionally with some extra bytes at the
|
|
||||||
* end.
|
|
||||||
*/
|
|
||||||
id GCAllocateObjectWithZone(Class cls, void *zone, size_t extraBytes);
|
|
||||||
/**
|
|
||||||
* Allocates a buffer of the specified size. The third parameter indicates
|
|
||||||
* whether this this memory should be scanned for untracked references. This
|
|
||||||
* buffer itself will be freed when the last reference to it is lost. If the
|
|
||||||
* scan parameter is set to YES then pointer assignments in this region should
|
|
||||||
* not use strong-cast assigns or GCRetain().
|
|
||||||
*/
|
|
||||||
void *GCAllocateBufferWithZone(void *zone, size_t size, BOOL scan);
|
|
||||||
|
|
||||||
void GCFreeObject(id object);
|
|
||||||
void GCFreeObjectUnsafe(id object);
|
|
||||||
@ -1,220 +0,0 @@
|
|||||||
#include "../objc/runtime.h"
|
|
||||||
#import "malloc.h"
|
|
||||||
#import "object.h"
|
|
||||||
#import "thread.h"
|
|
||||||
#import "trace.h"
|
|
||||||
#import "cycle.h"
|
|
||||||
#import "visit.h"
|
|
||||||
#import "workqueue.h"
|
|
||||||
#import "static.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pointer comparison. Needed for the hash table.
|
|
||||||
*/
|
|
||||||
static int pointer_compare(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
static int pointer_hash(const void *obj)
|
|
||||||
{
|
|
||||||
intptr_t ptr = (intptr_t)obj;
|
|
||||||
return (ptr >> 8) | (ptr << 8);
|
|
||||||
}
|
|
||||||
#define MAP_TABLE_NAME known_object
|
|
||||||
#define MAP_TABLE_COMPARE_FUNCTION pointer_compare
|
|
||||||
#define MAP_TABLE_HASH_KEY pointer_hash
|
|
||||||
#define MAP_TABLE_HASH_VALUE pointer_hash
|
|
||||||
#include "../hash_table.h"
|
|
||||||
|
|
||||||
@interface GCObject
|
|
||||||
- (void)finalize;
|
|
||||||
@end
|
|
||||||
|
|
||||||
static void* malloc_zone_alloc(void *zone, size_t bytes)
|
|
||||||
{
|
|
||||||
return calloc(1, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
void *(*gc_alloc_with_zone)(void *zone, size_t bytes) = malloc_zone_alloc;
|
|
||||||
|
|
||||||
static void malloc_zone_free(void *zone, void *mem)
|
|
||||||
{
|
|
||||||
free(mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void (*gc_free_with_zone)(void *zone, void *mem) = malloc_zone_free;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Macro for calculating the size of a header structure, including padding
|
|
||||||
* required for alignment.
|
|
||||||
*/
|
|
||||||
#define headerSize(header)\
|
|
||||||
({\
|
|
||||||
size_t headerSize = sizeof(struct header);\
|
|
||||||
/* Everything else expects the isa pointer to be correctly aligned and all\
|
|
||||||
* subsequent ivars will be placed with the assumption that they have the\
|
|
||||||
* correct alignment, so make sure this is really the case. */\
|
|
||||||
if (headerSize % __alignof(void*))\
|
|
||||||
{\
|
|
||||||
headerSize += headerSize % __alignof(void*);\
|
|
||||||
}\
|
|
||||||
headerSize;\
|
|
||||||
})
|
|
||||||
|
|
||||||
id GCAllocateObjectWithZone(Class cls, void *zone, size_t extraBytes)
|
|
||||||
{
|
|
||||||
// Allocate space for the header and ivars.
|
|
||||||
size_t allocSize = headerSize(gc_object_header) + class_getInstanceSize(cls);
|
|
||||||
// And for the extra space that we were asked for.
|
|
||||||
allocSize += extraBytes;
|
|
||||||
struct gc_object_header *region = gc_alloc_with_zone(zone, allocSize);
|
|
||||||
region->zone = zone;
|
|
||||||
id obj = (id)((char*)region + headerSize(gc_object_header));
|
|
||||||
obj->isa = cls;
|
|
||||||
// Reference count is 0, so set visited to prevent it from being collected
|
|
||||||
// immediately
|
|
||||||
GCSetFlag(obj, GCFlagVisited);
|
|
||||||
// Mark as free or in use.
|
|
||||||
GCSetColourOfObject(obj, GCColourBlack);
|
|
||||||
// Add to traced map later, if it hasn't been retained
|
|
||||||
GCAddObject(obj);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *GCAllocateBufferWithZone(void *zone, size_t size, BOOL scan)
|
|
||||||
{
|
|
||||||
size_t allocSize = headerSize(gc_buffer_header) + size;
|
|
||||||
struct gc_buffer_header *region = gc_alloc_with_zone(zone, allocSize);
|
|
||||||
region->size = size;
|
|
||||||
region->object_header.zone = zone;
|
|
||||||
char *buffer = ((char*)region) + headerSize(gc_buffer_header);
|
|
||||||
if (scan)
|
|
||||||
{
|
|
||||||
GCAddBufferForTracing(region);
|
|
||||||
}
|
|
||||||
// Reference count is 0, so set visited to prevent it from being collected
|
|
||||||
// immediately
|
|
||||||
GCSetFlag((id)buffer, GCFlagVisited);
|
|
||||||
GCSetFlag((id)buffer, GCFlagNotObject);
|
|
||||||
// Mark as free or in use.
|
|
||||||
GCSetColourOfObject((id)buffer, GCColourBlack);
|
|
||||||
// Add to traced map later, if it hasn't been retained
|
|
||||||
GCAddObject((id)buffer);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void freeObject(id object)
|
|
||||||
{
|
|
||||||
void * addr;
|
|
||||||
if (GCTestFlag(object, GCFlagNotObject))
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Freeing bufer %x\n", (int)object);
|
|
||||||
addr = GCHeaderForBuffer(object);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
addr = GCHeaderForObject(object);
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Freeing %x\n", (int)object);
|
|
||||||
gc_free_with_zone(GCHeaderForObject(object)->zone, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCWeakRelease(id anObject)
|
|
||||||
{
|
|
||||||
if (!GCObjectIsDynamic(anObject)) { return; }
|
|
||||||
long count = GCDecrementWeakCount(anObject);
|
|
||||||
// If the object has been finalized and this is the last weak ref, free it.
|
|
||||||
if (count == 0 && GCColourOfObject(anObject) == GCColourOrange)
|
|
||||||
{
|
|
||||||
freeObject(anObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id GCWeakRetain(id anObject)
|
|
||||||
{
|
|
||||||
if (!GCObjectIsDynamic(anObject)) { return anObject; }
|
|
||||||
// If this object has already been finalized, return nil.
|
|
||||||
if (GCColourOfObject(anObject) == GCColourOrange)
|
|
||||||
{
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
GCIncrementWeakCount(anObject);
|
|
||||||
return anObject;
|
|
||||||
}
|
|
||||||
// NOTE: Weak read should add the object for tracing.
|
|
||||||
|
|
||||||
void GCAddObjectForTracing(id object);
|
|
||||||
|
|
||||||
static BOOL foundRedObjects;
|
|
||||||
|
|
||||||
static void releaseObjects(id object, void *context, BOOL isWeak)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Releasing %x\n", (int)object);
|
|
||||||
if (isWeak)
|
|
||||||
{
|
|
||||||
GCWeakRelease(object);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GCColour colour = GCColourOfObject(object);
|
|
||||||
// If we're freeing a cycle, mark this object as orange, finalize it,
|
|
||||||
// then tell the tracing code to really delete it later
|
|
||||||
if (colour == GCColourRed)
|
|
||||||
{
|
|
||||||
foundRedObjects = YES;
|
|
||||||
GCSetColourOfObject(object, GCColourOrange);
|
|
||||||
[object finalize];
|
|
||||||
}
|
|
||||||
else if (colour != GCColourOrange)
|
|
||||||
{
|
|
||||||
GCRelease(object);
|
|
||||||
}
|
|
||||||
//fprintf(stderr, "Object has refcount %d\n", (int)GCGetRetainCount(object));
|
|
||||||
if (GCGetRetainCount(object) <= 0)
|
|
||||||
{
|
|
||||||
GCAddObjectForTracing(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finalizes an object and frees it if its weak reference count is 0.
|
|
||||||
*
|
|
||||||
* This version must only be called from the GC thread.
|
|
||||||
*/
|
|
||||||
void GCFreeObjectUnsafe(id object)
|
|
||||||
{
|
|
||||||
if (!GCObjectIsDynamic(object)) { return; }
|
|
||||||
|
|
||||||
//fprintf(stderr, "Going to Free object %x\n", (int)(object));
|
|
||||||
|
|
||||||
foundRedObjects = NO;
|
|
||||||
if (GCColourOrange != GCSetColourOfObject(object, GCColourOrange))
|
|
||||||
{
|
|
||||||
// If this is really an object, kill all of its references and then
|
|
||||||
// finalize it.
|
|
||||||
if (!GCTestFlag(object, GCFlagNotObject))
|
|
||||||
{
|
|
||||||
if (0)
|
|
||||||
GCVisitChildren(object, releaseObjects, NULL, YES);
|
|
||||||
[object finalize];
|
|
||||||
}
|
|
||||||
// FIXME: Implement this.
|
|
||||||
//GCRemoveRegionFromTracingUnsafe(region);
|
|
||||||
}
|
|
||||||
if (GCGetWeakRefCount(object) == 0)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Freeing object %x\n", (int)(object));
|
|
||||||
freeObject(object);
|
|
||||||
}
|
|
||||||
if (foundRedObjects)
|
|
||||||
{
|
|
||||||
GCRunTracerIfNeeded(NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCFreeObject(id object)
|
|
||||||
{
|
|
||||||
GCPerform((void(*)(void*))GCFreeObjectUnsafe, object);
|
|
||||||
}
|
|
||||||
@ -1,233 +0,0 @@
|
|||||||
/**
|
|
||||||
* object.h defines the layout of the object header for GCKit-managed objects.
|
|
||||||
* These objects are allocated with GCAllocateObject() and are not freed by
|
|
||||||
* code outside of GCKit.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GCINLINEPUBLIC functions are functions that are inline for GCKit but
|
|
||||||
* exported symbols for the rest of the world.
|
|
||||||
*/
|
|
||||||
#ifndef GCINLINEPUBLIC
|
|
||||||
#define GCINLINEPUBLIC inline static
|
|
||||||
#endif
|
|
||||||
/**
|
|
||||||
* GCINLINEPRIVATE functions are inline in GCKit and are not exported.
|
|
||||||
*/
|
|
||||||
#define GCINLINEPRIVATE inline static __attribute__((unused))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified version of the object header. Stores a 16-bit reference count and
|
|
||||||
* a 16-bit flags field. Three bits of the flags are used for the object
|
|
||||||
* colour and one to indicate if it is buffered.
|
|
||||||
*
|
|
||||||
* Note: On 64-bit platforms we have to add some padding, so it might be better
|
|
||||||
* to make the ref countfields bigger.
|
|
||||||
*/
|
|
||||||
__attribute__((packed))
|
|
||||||
struct gc_object_header
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Garbage collection Flags associated with this object. This includes the
|
|
||||||
* object's colour while performing cycle detection. */
|
|
||||||
char flags;
|
|
||||||
/**
|
|
||||||
* Number of weak references held to this object. An object may be
|
|
||||||
* finalized, but may not be deleted while weak references are held to it.
|
|
||||||
*/
|
|
||||||
char weak_ref_count;
|
|
||||||
/**
|
|
||||||
* Number of strong references to the object. This count is modified by
|
|
||||||
* GCRetain() and GCRelease(). When it reaches 0, the object has no strong
|
|
||||||
* references to it. It may, however, have references from the stack or
|
|
||||||
* traced memory. When the strong reference count reaches 0, the object
|
|
||||||
* will be added to the trace pile.
|
|
||||||
*/
|
|
||||||
short strong_ref_count;
|
|
||||||
/**
|
|
||||||
* The allocation zone for this object. This is an opaque pointer from the
|
|
||||||
* perspective of GCKit. In GNUstep, this will be an NSZone.
|
|
||||||
*/
|
|
||||||
void *zone;
|
|
||||||
};
|
|
||||||
|
|
||||||
__attribute__((packed))
|
|
||||||
struct gc_buffer_header
|
|
||||||
{
|
|
||||||
size_t size;
|
|
||||||
struct gc_object_header object_header;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycle detection is a graph colouring algorithm. This type specifies the
|
|
||||||
* possible colours.
|
|
||||||
*/
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
/** Acyclic */
|
|
||||||
GCColourGreen = 0,
|
|
||||||
/** In use or free. */
|
|
||||||
GCColourBlack = 1,
|
|
||||||
/** Possible member of a cycle. */
|
|
||||||
GCColourGrey = 2,
|
|
||||||
/** Member of a garbage cycle. */
|
|
||||||
GCColourWhite = 3,
|
|
||||||
/** Potential root of a cycle. */
|
|
||||||
GCColourPurple = 4,
|
|
||||||
/** Object currently being freed. */
|
|
||||||
GCColourOrange = 5,
|
|
||||||
/** Object is a member of a cycle to be freed when the last traced
|
|
||||||
* reference is removed, or resurrected if retained. */
|
|
||||||
GCColourRed = 6
|
|
||||||
} GCColour;
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
/** Set when the object has been added to the potential-garbage list. */
|
|
||||||
GCFlagBuffered = (1<<3),
|
|
||||||
/** Set when an object has been assigned on a traced part of the heap. */
|
|
||||||
GCFlagEscaped = (1<<4),
|
|
||||||
/** Visited by the tracing code. */
|
|
||||||
GCFlagVisited = (1<<5),
|
|
||||||
/** This object is a memory buffer, not an Objective-C object. */
|
|
||||||
GCFlagNotObject = (1<<6),
|
|
||||||
/** Object uses CoreFoundation-style semantics and won't ever by traced. */
|
|
||||||
GCFlagCFObject = (1<<7)
|
|
||||||
} GCFlag;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debugging function used to return a colour as a human-readable string.
|
|
||||||
*/
|
|
||||||
__attribute__((unused))
|
|
||||||
inline static const char *GCStringFromColour(GCColour aColour)
|
|
||||||
{
|
|
||||||
switch(aColour)
|
|
||||||
{
|
|
||||||
case GCColourBlack: return "black";
|
|
||||||
case GCColourGrey: return "grey";
|
|
||||||
case GCColourWhite: return "white";
|
|
||||||
case GCColourPurple: return "purple";
|
|
||||||
case GCColourGreen: return "green";
|
|
||||||
case GCColourOrange: return "orange";
|
|
||||||
case GCColourRed: return "red";
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
GCINLINEPRIVATE struct gc_object_header*GCHeaderForObject(id anObject)
|
|
||||||
{
|
|
||||||
return &((struct gc_object_header*)anObject)[-1];
|
|
||||||
}
|
|
||||||
GCINLINEPRIVATE struct gc_buffer_header*GCHeaderForBuffer(id anObject)
|
|
||||||
{
|
|
||||||
return &((struct gc_buffer_header*)anObject)[-1];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the flags for a specified object.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE unsigned short GCObjectFlags(id anObject)
|
|
||||||
{
|
|
||||||
return GCHeaderForObject(anObject)->flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the colour of the specified object.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE GCColour GCColourOfObject(id anObject)
|
|
||||||
{
|
|
||||||
// Lowest 3 bits of the flags field contain the colour.
|
|
||||||
return GCObjectFlags(anObject) & 0x7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to set the flags for a given object. Returns the old value.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE unsigned short GCTrySetFlags(id anObject, unsigned char old,
|
|
||||||
unsigned char value)
|
|
||||||
{
|
|
||||||
return __sync_bool_compare_and_swap(
|
|
||||||
&(((struct gc_object_header*)anObject)[-1].flags), old, value);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets the colour of the specified object, returning the old colour
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE GCColour GCSetColourOfObject(id anObject, GCColour colour)
|
|
||||||
{
|
|
||||||
char oldFlags;
|
|
||||||
char newFlags;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldFlags = GCObjectFlags(anObject);
|
|
||||||
newFlags = oldFlags;
|
|
||||||
// Clear the old colour.
|
|
||||||
newFlags &= 0xf8;
|
|
||||||
// Set the new colour
|
|
||||||
newFlags |= colour;
|
|
||||||
} while(!GCTrySetFlags(anObject, oldFlags, newFlags));
|
|
||||||
return oldFlags & 0x7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the specified flag for a given object.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE void GCSetFlag(id anObject, GCFlag flag)
|
|
||||||
{
|
|
||||||
unsigned oldFlags;
|
|
||||||
unsigned newFlags;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldFlags = GCObjectFlags(anObject);
|
|
||||||
newFlags = oldFlags;
|
|
||||||
newFlags |= flag;
|
|
||||||
} while(!GCTrySetFlags(anObject, oldFlags, newFlags));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the specified flag on an object.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE void GCClearFlag(id anObject, GCFlag flag)
|
|
||||||
{
|
|
||||||
unsigned oldFlags;
|
|
||||||
unsigned newFlags;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldFlags = GCObjectFlags(anObject);
|
|
||||||
newFlags = oldFlags;
|
|
||||||
newFlags &= ~flag;
|
|
||||||
} while(!GCTrySetFlags(anObject, oldFlags, newFlags));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the specified object's buffered flag is set.
|
|
||||||
*/
|
|
||||||
GCINLINEPRIVATE BOOL GCTestFlag(id anObject, GCFlag flag)
|
|
||||||
{
|
|
||||||
return GCObjectFlags(anObject) & flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
GCINLINEPUBLIC long GCGetRetainCount(id anObject)
|
|
||||||
{
|
|
||||||
unsigned short refcount = ((struct gc_object_header*)anObject)[-1].strong_ref_count;
|
|
||||||
return (long) refcount;
|
|
||||||
}
|
|
||||||
GCINLINEPRIVATE long GCDecrementRetainCount(id anObject)
|
|
||||||
{
|
|
||||||
return __sync_sub_and_fetch(&(GCHeaderForObject(anObject)->strong_ref_count), 1);
|
|
||||||
}
|
|
||||||
GCINLINEPRIVATE long GCIncrementRetainCount(id anObject)
|
|
||||||
{
|
|
||||||
return __sync_add_and_fetch(&(GCHeaderForObject(anObject)->strong_ref_count), 1);
|
|
||||||
}
|
|
||||||
GCINLINEPUBLIC long GCGetWeakRefCount(id anObject)
|
|
||||||
{
|
|
||||||
unsigned short refcount = ((struct gc_object_header*)anObject)[-1].weak_ref_count;
|
|
||||||
return (long) refcount;
|
|
||||||
}
|
|
||||||
|
|
||||||
GCINLINEPRIVATE long GCDecrementWeakCount(id anObject)
|
|
||||||
{
|
|
||||||
return __sync_sub_and_fetch(&(GCHeaderForObject(anObject)->weak_ref_count), 1);
|
|
||||||
}
|
|
||||||
GCINLINEPRIVATE long GCIncrementWeakCount(id anObject)
|
|
||||||
{
|
|
||||||
return __sync_add_and_fetch(&(GCHeaderForObject(anObject)->weak_ref_count), 1);
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an object is in one of the sections that the loader allocated. If
|
|
||||||
* so, it won't have a GCKit header so we just assume that it never needs
|
|
||||||
* collecting.
|
|
||||||
*/
|
|
||||||
static inline BOOL GCObjectIsDynamic(id obj)
|
|
||||||
{
|
|
||||||
Dl_info i;
|
|
||||||
return !dladdr(obj, &i);
|
|
||||||
}
|
|
||||||
@ -1,244 +0,0 @@
|
|||||||
#if 0
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// TESTING:
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple object which stores pointers to two objects. Used to test whether
|
|
||||||
* cycle detection is really working by creating garbage cycles and checking
|
|
||||||
* that they are free'd.
|
|
||||||
*/
|
|
||||||
@interface Pair
|
|
||||||
{
|
|
||||||
Class isa;
|
|
||||||
@public
|
|
||||||
id a, b;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@implementation Pair
|
|
||||||
/**
|
|
||||||
* Create a new pair and enable cycle detection for it.
|
|
||||||
*/
|
|
||||||
+ (id) new
|
|
||||||
{
|
|
||||||
id new = GCAllocateObjectWithZone(self, NULL, o);
|
|
||||||
// Enable automatic cycle detection for this object.
|
|
||||||
setColourOfObject(new, black);
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Release both pointers and log that the object has been freed.
|
|
||||||
*/
|
|
||||||
- (void) dealloc
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Pair destroyed\n");
|
|
||||||
[a release];
|
|
||||||
[b release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
int main(int argc, char **argv, char **env)
|
|
||||||
{
|
|
||||||
id pool = [GCAutoreleasePool new];
|
|
||||||
// FIXME: Test object -> traced region -> object
|
|
||||||
Pair * a1 = [Pair new];
|
|
||||||
Pair * a2 = [Pair new];
|
|
||||||
Pair * a3 = [Pair new];
|
|
||||||
Pair * a4 = [Pair new];
|
|
||||||
Pair * a5 = [Pair new];
|
|
||||||
a1->a = [a2 retain];
|
|
||||||
a1->b = [a5 retain];
|
|
||||||
a2->a = [a2 retain];
|
|
||||||
a2->b = [a4 retain];
|
|
||||||
a3->a = [a3 retain];
|
|
||||||
a3->b = [a4 retain];
|
|
||||||
a4->a = [a3 retain];
|
|
||||||
a4->b = [a5 retain];
|
|
||||||
a5->a = [a5 retain];
|
|
||||||
a5->b = [a1 retain];
|
|
||||||
a5->b = [NSObject new];
|
|
||||||
visObject(a1, @"Test");
|
|
||||||
// Check that we haven't broken anything yet...
|
|
||||||
NSLog(@"Testing? %@", a1);
|
|
||||||
[a1 release];
|
|
||||||
[a2 release];
|
|
||||||
[a3 release];
|
|
||||||
[a4 release];
|
|
||||||
[a5 release];
|
|
||||||
//[pool drain];
|
|
||||||
[pool release];
|
|
||||||
//fprintf(stderr, "Buffered Objects: %d\n", loopBufferInsert);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#include "../objc/runtime.h"
|
|
||||||
#import "malloc.h"
|
|
||||||
#import "thread.h"
|
|
||||||
#import "trace.h"
|
|
||||||
#import "cycle.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
@interface NSConstantString
|
|
||||||
{
|
|
||||||
id isa;
|
|
||||||
char *c_str;
|
|
||||||
unsigned len;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface SimpleObject
|
|
||||||
{
|
|
||||||
Class isa;
|
|
||||||
}
|
|
||||||
+ (id)new;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SimpleObject
|
|
||||||
+ (id)new
|
|
||||||
{
|
|
||||||
id obj = GCAllocateObjectWithZone(self, NULL, 0);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
- (void)log
|
|
||||||
{
|
|
||||||
printf("Simple object %x is still alive\n", (int)self);
|
|
||||||
}
|
|
||||||
- (void)finalize
|
|
||||||
{
|
|
||||||
printf("%s %x finalised\n", class_getName(isa), (int)self);
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
// The test program calls GCDrain() repeatedly to force the GC to run. In real
|
|
||||||
// code, this will be triggered automatically as a result of object allocations
|
|
||||||
// and reference count changes. In this code, however, it is not. The test
|
|
||||||
// case will exit before the GC would run in normal use. This is not a bug;
|
|
||||||
// there's no point spending CPU time collecting objects a few milliseconds
|
|
||||||
// before the process exits and the OS reclaims them all at once. The point of
|
|
||||||
// a garbage collector is to reclaim memory for reuse, and if no reuse is going
|
|
||||||
// to take place, there is no point reclaiming it.
|
|
||||||
|
|
||||||
void makeObject(void)
|
|
||||||
{
|
|
||||||
SimpleObject *foo = [SimpleObject new];
|
|
||||||
[foo log];
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
[foo log];
|
|
||||||
foo = nil;
|
|
||||||
[foo log];
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void doStuff(void)
|
|
||||||
{
|
|
||||||
makeObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void makeRefCountedObject(void)
|
|
||||||
{
|
|
||||||
SimpleObject *foo = [SimpleObject new];
|
|
||||||
GCRelease(GCRetain(foo));
|
|
||||||
[foo log];
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void doRefCountStuff(void)
|
|
||||||
{
|
|
||||||
makeRefCountedObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
static id *buffer;
|
|
||||||
|
|
||||||
void putObjectInBuffer(void)
|
|
||||||
{
|
|
||||||
buffer = (id*)GCRetain((id)GCAllocateBufferWithZone(NULL, sizeof(id), YES));
|
|
||||||
buffer[0] = objc_assign_strongCast([SimpleObject new], buffer);
|
|
||||||
//fprintf(stderr, "Storing pointer %x in traced memory %x\n", (int)buffer[0], (int)buffer);
|
|
||||||
[*buffer log];
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTracedMemory(void)
|
|
||||||
{
|
|
||||||
putObjectInBuffer();
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
@interface Pair : SimpleObject
|
|
||||||
{
|
|
||||||
@public
|
|
||||||
Pair *a, *b;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@implementation Pair @end
|
|
||||||
|
|
||||||
void makeObjectCycle(void)
|
|
||||||
{
|
|
||||||
Pair *obj = [Pair new];
|
|
||||||
obj->a = GCRetain([Pair new]);
|
|
||||||
obj->b = GCRetain([Pair new]);
|
|
||||||
obj->a->a = GCRetain(obj->b);
|
|
||||||
obj->b->b = GCRetain(obj->a);
|
|
||||||
obj->a->b = GCRetain(obj);
|
|
||||||
obj->b->a = GCRetain(obj);
|
|
||||||
[obj log];
|
|
||||||
GCRelease(GCRetain(obj));
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testCycle(void)
|
|
||||||
{
|
|
||||||
makeObjectCycle();
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
}
|
|
||||||
|
|
||||||
void makeTracedCycle(void)
|
|
||||||
{
|
|
||||||
// These two buffers are pointing to each other
|
|
||||||
id *b1 = GCAllocateBufferWithZone(NULL, sizeof(id), YES);
|
|
||||||
Pair *p = [Pair new];
|
|
||||||
id *b2 = GCAllocateBufferWithZone(NULL, sizeof(id), YES);
|
|
||||||
fprintf(stderr, "Expected to leak %x and %x\n", (int)b1, (int)b2);
|
|
||||||
objc_assign_strongCast((id)b2, b1);
|
|
||||||
//objc_assign_strongCast(p, b1);
|
|
||||||
objc_assign_strongCast((id)b1, b2);
|
|
||||||
p->a = GCRetain((id)b2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTracedCycle(void)
|
|
||||||
{
|
|
||||||
makeTracedCycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
testTracedCycle();
|
|
||||||
/*
|
|
||||||
// Not required on main thread:
|
|
||||||
//GCRegisterThread();
|
|
||||||
doStuff();
|
|
||||||
GCDrain(YES);
|
|
||||||
doRefCountStuff();
|
|
||||||
GCDrain(YES);
|
|
||||||
testTracedMemory();
|
|
||||||
buffer[0] = objc_assign_strongCast(nil, buffer);
|
|
||||||
GCDrain(YES);
|
|
||||||
|
|
||||||
testCycle();
|
|
||||||
*/
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
sched_yield();
|
|
||||||
GCDrain(YES);
|
|
||||||
GCDrain(YES);
|
|
||||||
printf("Waiting to make sure the GC thread has caught up before the test exits\n");
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Modified autorelease pool which performs automatic detection and collection
|
|
||||||
* of garbage cycles.
|
|
||||||
*/
|
|
||||||
typedef struct _GCThread
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Next thread in the list.
|
|
||||||
*/
|
|
||||||
struct _GCThread *next;
|
|
||||||
/**
|
|
||||||
* Last thread in the list.
|
|
||||||
*/
|
|
||||||
struct _GCThread *last;
|
|
||||||
/**
|
|
||||||
* Map of objects that haven't yet escaped from the thread.
|
|
||||||
*/
|
|
||||||
void *unescapedObjects;
|
|
||||||
/**
|
|
||||||
* Top of the stack.
|
|
||||||
*/
|
|
||||||
void *stackTop;
|
|
||||||
/**
|
|
||||||
* Top of the stack.
|
|
||||||
*/
|
|
||||||
void *stackBottom;
|
|
||||||
/**
|
|
||||||
* Per-thread buffer into which objects that are potentially roots in garbage
|
|
||||||
* cycles are stored.
|
|
||||||
*/
|
|
||||||
id *cycleBuffer;
|
|
||||||
/**
|
|
||||||
* Insert point into cycle buffer
|
|
||||||
*/
|
|
||||||
unsigned int cycleBufferInsert;
|
|
||||||
/**
|
|
||||||
* Buffer for objects whose reference count has reached 0. These may be
|
|
||||||
* freed if there are no references to them on the stack.
|
|
||||||
*/
|
|
||||||
id *freeBuffer;
|
|
||||||
/**
|
|
||||||
* Insert point into to-free buffer
|
|
||||||
*/
|
|
||||||
unsigned int freeBufferInsert;
|
|
||||||
/**
|
|
||||||
* Condition variable prevents the thread from really exiting (and having
|
|
||||||
* its stack deallocated) until the GC thread has removed the thread.
|
|
||||||
*/
|
|
||||||
void *exitCondition;
|
|
||||||
/**
|
|
||||||
* The generation when this stack was last scanned.
|
|
||||||
*/
|
|
||||||
volatile int scannedInGeneration;
|
|
||||||
} GCThread;
|
|
||||||
|
|
||||||
extern GCThread *GCThreadList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the current thread for garbage collection.
|
|
||||||
*/
|
|
||||||
void GCRegisterThread(void);
|
|
||||||
/**
|
|
||||||
* Adds an object for tracing or cycle detection or tracing.
|
|
||||||
*/
|
|
||||||
void GCAddObject(id anObject);
|
|
||||||
/**
|
|
||||||
* Drains the objects queued for (potential) collection on the current thread.
|
|
||||||
* Passing YES as the argument forces a full sweep of the heap-allocated traced
|
|
||||||
* regions.
|
|
||||||
*
|
|
||||||
* Note that this method performs the collection in a second thread, so some
|
|
||||||
* objects may not be collected until after it has run.
|
|
||||||
*/
|
|
||||||
void GCDrain(BOOL forceCollect);
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Structure identifying regions in memory. The region is treated as being
|
|
||||||
*/
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
struct gc_buffer_header *buffer;
|
|
||||||
void *start;
|
|
||||||
void *end;
|
|
||||||
} GCTracedRegion;
|
|
||||||
|
|
||||||
|
|
||||||
void GCRunTracerIfNeeded(BOOL);
|
|
||||||
|
|
||||||
void GCAddObjectsForTracing(GCThread *thr);
|
|
||||||
void GCTraceStackSynchronous(GCThread *thr);
|
|
||||||
|
|
||||||
|
|
||||||
void GCAddBufferForTracing(struct gc_buffer_header *buffer);
|
|
||||||
|
|
||||||
extern volatile int GCGeneration;
|
|
||||||
void GCCollect();
|
|
||||||
id objc_assign_strongCast(id obj, id *ptr);
|
|
||||||
@ -1,744 +0,0 @@
|
|||||||
/**
|
|
||||||
* trace.m implements the tracing part of the collector. This is responsible
|
|
||||||
* for checking for references to objects on stacks and in traced regions on
|
|
||||||
* the heap before finally freeing them.
|
|
||||||
*/
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/limits.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include "../objc/runtime.h"
|
|
||||||
#import "object.h"
|
|
||||||
#import "thread.h"
|
|
||||||
#import "trace.h"
|
|
||||||
#import "cycle.h"
|
|
||||||
#import "malloc.h"
|
|
||||||
#import "static.h"
|
|
||||||
#import "workqueue.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure storing pointers that are currently being traced.
|
|
||||||
*
|
|
||||||
* We store the last location where an object was seen so that, when no objects
|
|
||||||
* are found, we can
|
|
||||||
*
|
|
||||||
* TODO: We currently don't use the last-seen addresses for objects. If we
|
|
||||||
* did, we could quickly verify that the last reference to them was still valid
|
|
||||||
* and eliminate some
|
|
||||||
*/
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
/** The object that might be ready to be freed. */
|
|
||||||
id pointer;
|
|
||||||
/** Last on-heap address that we saw for this object. */
|
|
||||||
id *heapAddress;
|
|
||||||
/** Highest on-stack address that we saw for this object. */
|
|
||||||
id *stackAddress;
|
|
||||||
/** Sweep pass number when this object's visited flag was cleared. All
|
|
||||||
* traced regions - stack and heap - must have been traced at least once
|
|
||||||
* before this object can be freed. */
|
|
||||||
int visitClearedGeneration;
|
|
||||||
} GCTracedPointer;
|
|
||||||
|
|
||||||
// HUGE FIXME: Handle wrapping of this sensibly.
|
|
||||||
volatile int GCGeneration;
|
|
||||||
|
|
||||||
static const GCTracedPointer GCNullTracedPointer = {0,0};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pointer comparison. Needed for the hash table.
|
|
||||||
*/
|
|
||||||
static int traced_pointer_compare(const void *a, const GCTracedPointer b)
|
|
||||||
{
|
|
||||||
return a == b.pointer;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Pointer hash function. The lowest bits of a pointer have very little
|
|
||||||
* entropy - we have lots of objects the same size and alignment, so they will
|
|
||||||
* end up at the same place within a page.
|
|
||||||
*/
|
|
||||||
static int traced_pointer_hash(const GCTracedPointer obj)
|
|
||||||
{
|
|
||||||
intptr_t ptr = (intptr_t)obj.pointer;
|
|
||||||
return (ptr >> 4) | (ptr << 4);
|
|
||||||
}
|
|
||||||
static int traced_pointer_key_hash(const void *obj)
|
|
||||||
{
|
|
||||||
intptr_t ptr = (intptr_t)obj;
|
|
||||||
return (ptr >> 4) | (ptr << 4);
|
|
||||||
}
|
|
||||||
static int traced_pointer_is_null(const GCTracedPointer obj)
|
|
||||||
{
|
|
||||||
return obj.pointer == NULL;
|
|
||||||
}
|
|
||||||
#define MAP_TABLE_NAME traced_object
|
|
||||||
#define MAP_TABLE_COMPARE_FUNCTION traced_pointer_compare
|
|
||||||
#define MAP_TABLE_HASH_VALUE traced_pointer_hash
|
|
||||||
#define MAP_TABLE_HASH_KEY traced_pointer_key_hash
|
|
||||||
#define MAP_TABLE_VALUE_TYPE GCTracedPointer
|
|
||||||
#define MAP_TABLE_VALUE_NULL traced_pointer_is_null
|
|
||||||
#define MAP_TABLE_VALUE_PLACEHOLDER GCNullTracedPointer
|
|
||||||
#define MAP_TABLE_NO_LOCK
|
|
||||||
|
|
||||||
#include "../hash_table.h"
|
|
||||||
/**
|
|
||||||
* Pointer comparison. Needed for the hash table.
|
|
||||||
*/
|
|
||||||
static int pointer_compare(const void *a, const void *b)
|
|
||||||
{
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
#define MAP_TABLE_NAME unescaped_object
|
|
||||||
#define MAP_TABLE_COMPARE_FUNCTION pointer_compare
|
|
||||||
#define MAP_TABLE_HASH_KEY traced_pointer_key_hash
|
|
||||||
#define MAP_TABLE_HASH_VALUE traced_pointer_key_hash
|
|
||||||
#define MAP_TABLE_NO_LOCK
|
|
||||||
#define MAP_TABLE_SINGLE_THREAD
|
|
||||||
|
|
||||||
#include "../hash_table.h"
|
|
||||||
|
|
||||||
static traced_object_table *traced_objects;
|
|
||||||
/**
|
|
||||||
* Read write lock for modifying the traced object set. The GC thread may read
|
|
||||||
* from the tree without acquiring this lock, but other threads must acquire a
|
|
||||||
* read lock before reading from it. Any thread must acquire the write lock
|
|
||||||
* before modifying the traced object set. Only the GC thread may remove
|
|
||||||
* objects, other threads may modify them.
|
|
||||||
*/
|
|
||||||
static pthread_rwlock_t traced_objects_lock;
|
|
||||||
|
|
||||||
typedef struct _GCTracedRegionTreeNode
|
|
||||||
{
|
|
||||||
GCTracedRegion region;
|
|
||||||
struct _GCTracedRegionTreeNode *child[2];
|
|
||||||
enum { RED, BLACK=0 } colour;
|
|
||||||
} GCTracedRegionTreeNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Root of a red-black tree used to store regions that are traced. Note that
|
|
||||||
* this is not protected by any locks. We ensure serialisation by doing both
|
|
||||||
* tracing and freeing of traced regions in the same thread.
|
|
||||||
*
|
|
||||||
* Red-black implementation based on Julienne Walker's public domain version.
|
|
||||||
*/
|
|
||||||
static GCTracedRegionTreeNode *GCRegionTreeRoot;
|
|
||||||
/**
|
|
||||||
* Compare two traced regions and return a value that can be compared to 0 to
|
|
||||||
* find their ordering.
|
|
||||||
*/
|
|
||||||
static int GCCompareRegions(GCTracedRegion region1, GCTracedRegion region2)
|
|
||||||
{
|
|
||||||
// Region 1 is before region 2
|
|
||||||
if (region1.end < region2.start)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Region 2 is before region 1
|
|
||||||
if (region1.start < region2.end)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Regions overlap
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GCTracedRegion mergeRegions(
|
|
||||||
GCTracedRegion region1, GCTracedRegion region2)
|
|
||||||
{
|
|
||||||
if (region1.start > region2.start)
|
|
||||||
{
|
|
||||||
region1.start = region2.start;
|
|
||||||
}
|
|
||||||
if (region1.end < region2.end)
|
|
||||||
{
|
|
||||||
region1.end = region2.end;
|
|
||||||
}
|
|
||||||
return region1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static GCTracedRegionTreeNode *GCTracedRegionTreeNodeCreate(GCTracedRegion region)
|
|
||||||
{
|
|
||||||
GCTracedRegionTreeNode *node = calloc(1, sizeof(GCTracedRegionTreeNode));
|
|
||||||
node->region = region;
|
|
||||||
node->colour = RED;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int isNodeRed(GCTracedRegionTreeNode *node)
|
|
||||||
{
|
|
||||||
return (node != NULL) && node->colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GCTracedRegionTreeNode *rotateTree(GCTracedRegionTreeNode *node,
|
|
||||||
int direction)
|
|
||||||
{
|
|
||||||
GCTracedRegionTreeNode *save = node->child[!direction];
|
|
||||||
|
|
||||||
node->child[!direction] = save->child[direction];
|
|
||||||
save->child[direction] = node;
|
|
||||||
|
|
||||||
node->colour = RED;
|
|
||||||
save->colour = BLACK;
|
|
||||||
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
|
|
||||||
static GCTracedRegionTreeNode *rotateTreeDouble(GCTracedRegionTreeNode *node,
|
|
||||||
int direction)
|
|
||||||
{
|
|
||||||
node->child[!direction] = rotateTree(node->child[!direction], !direction);
|
|
||||||
return rotateTree(node, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the red-black tree is really a red black tree and not a nonsense tree.
|
|
||||||
*/
|
|
||||||
static int debugTree(GCTracedRegionTreeNode *node)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
|
||||||
if (NULL == node)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
GCTracedRegionTreeNode *left = node->child[0];
|
|
||||||
GCTracedRegionTreeNode *right = node->child[1];
|
|
||||||
|
|
||||||
/* Consecutive red childs */
|
|
||||||
if (isNodeRed(node) )
|
|
||||||
{
|
|
||||||
assert(!(isNodeRed(left) || isNodeRed(right)) && "Red violation" );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Invalid binary search tree */
|
|
||||||
assert(left == NULL || (GCCompareRegions(left->region, node->region) < 0));
|
|
||||||
assert(right == NULL || (GCCompareRegions(right->region, node->region) > 0));
|
|
||||||
|
|
||||||
int leftHeight = debugTree(left);
|
|
||||||
int rightHeight = debugTree(right);
|
|
||||||
|
|
||||||
//assert(leftHeight == 0 || rightHeight ==0 || leftHeight == rightHeight);
|
|
||||||
|
|
||||||
/* Only count black children */
|
|
||||||
if (leftHeight != 0 && rightHeight != 0)
|
|
||||||
{
|
|
||||||
return isNodeRed(node) ? leftHeight : leftHeight + 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#endif //DEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively inserts a region into the correct location.
|
|
||||||
*/
|
|
||||||
static GCTracedRegionTreeNode *tracedRegionInsert(
|
|
||||||
GCTracedRegionTreeNode *root, GCTracedRegion region)
|
|
||||||
{
|
|
||||||
if (NULL == root)
|
|
||||||
{
|
|
||||||
return GCTracedRegionTreeNodeCreate(region);
|
|
||||||
}
|
|
||||||
int child = GCCompareRegions(root->region, region);
|
|
||||||
// If the regions overlap, just merge them. Note that this will only
|
|
||||||
// affect the structure of the tree if things have already gone badly wrong
|
|
||||||
// somewhere else, because memory regions can not be extended into already
|
|
||||||
// allocated regions unless you broke something.
|
|
||||||
if (child == 0)
|
|
||||||
{
|
|
||||||
root->region = mergeRegions(root->region, region);
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
// If root->region < region, child is -1. Make it 0 for this case and let
|
|
||||||
// it remain 1 for the other case. This gives us the index of the child
|
|
||||||
child = child > 0;
|
|
||||||
root->child[child] = tracedRegionInsert(root->child[child], region);
|
|
||||||
if (isNodeRed(root->child[child]))
|
|
||||||
{
|
|
||||||
if (isNodeRed(root->child[!child]))
|
|
||||||
{
|
|
||||||
root->colour = RED;
|
|
||||||
root->child[0]->colour = BLACK;
|
|
||||||
root->child[1]->colour = BLACK;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (isNodeRed(root->child[child]->child[child]))
|
|
||||||
{
|
|
||||||
root = rotateTree(root, !child);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
root = rotateTreeDouble(root, !child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the new region into the tree.
|
|
||||||
*/
|
|
||||||
__attribute__((unused))
|
|
||||||
static void GCTracedRegionInsert(GCTracedRegion region)
|
|
||||||
{
|
|
||||||
GCRegionTreeRoot = tracedRegionInsert(GCRegionTreeRoot, region);
|
|
||||||
GCRegionTreeRoot->colour = BLACK;
|
|
||||||
debugTree(GCRegionTreeRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((unused))
|
|
||||||
static void GCTracedRegionDelete(GCTracedRegion region)
|
|
||||||
{
|
|
||||||
if (GCRegionTreeRoot == NULL)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GCTracedRegionTreeNode head = {{0}}; /* False tree root */
|
|
||||||
GCTracedRegionTreeNode *q, *p, *g; /* Helpers */
|
|
||||||
GCTracedRegionTreeNode *f = NULL; /* Found item */
|
|
||||||
int dir = 1;
|
|
||||||
|
|
||||||
/* Set up helpers */
|
|
||||||
q = &head;
|
|
||||||
g = p = NULL;
|
|
||||||
q->child[1] = GCRegionTreeRoot;
|
|
||||||
|
|
||||||
/* Search and push a red down */
|
|
||||||
while ( q->child[dir] != NULL )
|
|
||||||
{
|
|
||||||
int last = dir;
|
|
||||||
|
|
||||||
/* Update helpers */
|
|
||||||
g = p, p = q;
|
|
||||||
q = q->child[dir];
|
|
||||||
dir = GCCompareRegions(q->region, region) < 0;
|
|
||||||
|
|
||||||
/* Save found node */
|
|
||||||
if (GCCompareRegions(q->region, region) == 0)
|
|
||||||
{
|
|
||||||
f = q;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Push the red node down */
|
|
||||||
if (!isNodeRed(q) && !isNodeRed(q->child[dir]))
|
|
||||||
{
|
|
||||||
if (isNodeRed (q->child[!dir]))
|
|
||||||
{
|
|
||||||
p = p->child[last] = rotateTree(q, dir);
|
|
||||||
}
|
|
||||||
else if (!isNodeRed(q->child[!dir]))
|
|
||||||
{
|
|
||||||
GCTracedRegionTreeNode *s = p->child[!last];
|
|
||||||
|
|
||||||
if (s != NULL)
|
|
||||||
{
|
|
||||||
if (!isNodeRed(s->child[!last]) && !isNodeRed(s->child[last]))
|
|
||||||
{
|
|
||||||
/* Color flip */
|
|
||||||
p->colour = 0;
|
|
||||||
s->colour = 1;
|
|
||||||
q->colour = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int dir2 = g->child[1] == p;
|
|
||||||
|
|
||||||
if (isNodeRed(s->child[last]))
|
|
||||||
{
|
|
||||||
g->child[dir2] = rotateTreeDouble(p, last);
|
|
||||||
}
|
|
||||||
else if (isNodeRed(s->child[!last]))
|
|
||||||
{
|
|
||||||
g->child[dir2] = rotateTree(p, last);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure correct coloring */
|
|
||||||
q->colour = g->child[dir2]->colour = RED;
|
|
||||||
g->child[dir2]->child[0]->colour = BLACK;
|
|
||||||
g->child[dir2]->child[1]->colour = BLACK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Replace and remove if found */
|
|
||||||
if (f != NULL)
|
|
||||||
{
|
|
||||||
f->region = q->region;
|
|
||||||
p->child[p->child[1] == q] =
|
|
||||||
q->child[q->child[0] == NULL];
|
|
||||||
free(q);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update root and make it black */
|
|
||||||
GCRegionTreeRoot = head.child[1];
|
|
||||||
if (GCRegionTreeRoot != NULL)
|
|
||||||
{
|
|
||||||
GCRegionTreeRoot->colour = BLACK;
|
|
||||||
}
|
|
||||||
debugTree(GCRegionTreeRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
typedef void(*gc_region_visitor)(GCTracedRegion, void*);
|
|
||||||
|
|
||||||
static void GCVisitTracedRegion(GCTracedRegionTreeNode *node,
|
|
||||||
gc_region_visitor visitor, void *context)
|
|
||||||
{
|
|
||||||
visitor(node->region, context);
|
|
||||||
if (node->child[0])
|
|
||||||
{
|
|
||||||
GCVisitTracedRegion(node->child[0], visitor, context);
|
|
||||||
}
|
|
||||||
if (node->child[1])
|
|
||||||
{
|
|
||||||
GCVisitTracedRegion(node->child[1], visitor, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCVisitTracedRegions(gc_region_visitor visitor, void *context)
|
|
||||||
{
|
|
||||||
if (GCRegionTreeRoot)
|
|
||||||
{
|
|
||||||
GCVisitTracedRegion(GCRegionTreeRoot, visitor, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
__attribute__((constructor))
|
|
||||||
static void GCTraceInitialise(void)
|
|
||||||
{
|
|
||||||
traced_objects = traced_object_create(128);
|
|
||||||
pthread_rwlock_init(&traced_objects_lock, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct GCTraceContext
|
|
||||||
{
|
|
||||||
int foundObjects;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void GCTraceRegion(GCTracedRegion region, void *c)
|
|
||||||
{
|
|
||||||
struct GCTraceContext *context = c;
|
|
||||||
// Stop if we've already found references to everything that might be
|
|
||||||
// garbage.
|
|
||||||
id *object = region.start;
|
|
||||||
fprintf(stderr, "Region starts at %x (%d bytes)\n", (int)object, (int)region.end - (int)region.start);
|
|
||||||
while (object < (id*)region.end)
|
|
||||||
{
|
|
||||||
if (context->foundObjects == traced_objects->table_used)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GCTracedPointer *foundObject = traced_object_table_get(traced_objects, *object);
|
|
||||||
if (foundObject && foundObject->pointer)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Found traced heap pointer to %x\n", (int)foundObject->pointer);
|
|
||||||
if(!GCTestFlag(foundObject->pointer, GCFlagVisited))
|
|
||||||
{
|
|
||||||
context->foundObjects++;
|
|
||||||
GCSetFlag(foundObject->pointer, GCFlagVisited);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
object++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Traces the current thread's stack.
|
|
||||||
*/
|
|
||||||
void GCTraceStackSynchronous(GCThread *thr)
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Scanning the stack...\n");
|
|
||||||
int generation = GCGeneration;
|
|
||||||
pthread_rwlock_rdlock(&traced_objects_lock);
|
|
||||||
if (NULL == thr->unescapedObjects)
|
|
||||||
{
|
|
||||||
thr->unescapedObjects = unescaped_object_create(256);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
struct unescaped_object_table_enumerator *e = NULL;
|
|
||||||
id ptr;
|
|
||||||
while ((ptr = unescaped_object_next(thr->unescapedObjects, &e)))
|
|
||||||
{
|
|
||||||
GCClearFlag(ptr, GCFlagVisited);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id *object = thr->stackBottom;
|
|
||||||
while (object < (id*)thr->stackTop)
|
|
||||||
{
|
|
||||||
if (unescaped_object_table_get(thr->unescapedObjects, *object))
|
|
||||||
{
|
|
||||||
// Note: This doesn't actually have to use atomic ops; this object
|
|
||||||
// is guaranteed, at this point, not to be referenced by another
|
|
||||||
// thread.
|
|
||||||
GCSetFlag(*object, GCFlagVisited);
|
|
||||||
//fprintf(stderr, "Tracing found %x\n", (int)*object);
|
|
||||||
}
|
|
||||||
GCTracedPointer *foundObject =
|
|
||||||
traced_object_table_get(traced_objects, *object);
|
|
||||||
// FIXME: This second test should not be required. Why are we being
|
|
||||||
// returned pointers to NULL?
|
|
||||||
if (foundObject && foundObject->pointer)
|
|
||||||
{
|
|
||||||
if(!GCTestFlag(foundObject->pointer, GCFlagVisited))
|
|
||||||
{
|
|
||||||
GCSetFlag(foundObject->pointer, GCFlagVisited);
|
|
||||||
if (foundObject->stackAddress)
|
|
||||||
{
|
|
||||||
if ((foundObject->stackAddress < (id*)thr->stackTop &&
|
|
||||||
foundObject->stackAddress > object))
|
|
||||||
{
|
|
||||||
foundObject->stackAddress = object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Record this address if there isn't an existing stack
|
|
||||||
// address.
|
|
||||||
foundObject->stackAddress = object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
object++;
|
|
||||||
}
|
|
||||||
pthread_rwlock_unlock(&traced_objects_lock);
|
|
||||||
|
|
||||||
struct unescaped_object_table_enumerator *e = NULL;
|
|
||||||
id ptr;
|
|
||||||
while ((ptr = unescaped_object_next(thr->unescapedObjects, &e)))
|
|
||||||
{
|
|
||||||
id oldPtr;
|
|
||||||
// Repeat on the current enumerator spot while we are are deleting things.
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldPtr = ptr;
|
|
||||||
if (!GCTestFlag(ptr, GCFlagVisited))
|
|
||||||
{
|
|
||||||
GCFreeObject(ptr);
|
|
||||||
unescaped_object_remove(thr->unescapedObjects, ptr);
|
|
||||||
}
|
|
||||||
} while ((oldPtr != (ptr = unescaped_object_current(thr->unescapedObjects, &e)))
|
|
||||||
&& ptr);
|
|
||||||
}
|
|
||||||
thr->scannedInGeneration = generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCRunTracer(void)
|
|
||||||
{
|
|
||||||
struct GCTraceContext context = {0};
|
|
||||||
// Mark any objects that we can see as really existing
|
|
||||||
GCVisitTracedRegions(GCTraceRegion, &context);
|
|
||||||
// Free any objects that we couldn't find references for
|
|
||||||
struct traced_object_table_enumerator *e = NULL;
|
|
||||||
int threadGeneration = INT_MAX;
|
|
||||||
for (GCThread *thr = GCThreadList ; thr != NULL ; thr = thr->next)
|
|
||||||
{
|
|
||||||
int thrGeneration = thr->scannedInGeneration;
|
|
||||||
if (thr->scannedInGeneration < threadGeneration)
|
|
||||||
{
|
|
||||||
threadGeneration = thrGeneration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GCTracedPointer *object;
|
|
||||||
while ((object = traced_object_next(traced_objects, &e)))
|
|
||||||
{
|
|
||||||
GCTracedPointer *oldPtr;
|
|
||||||
// Repeat on the current enumerator spot while we are are deleting things.
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldPtr = object;
|
|
||||||
//fprintf(stderr, "Thinking of freeing %x. Visited: %d, clear gen: %d, thread gen: %d\n", (int)object->pointer, GCTestFlag(object->pointer, GCFlagVisited), object->visitClearedGeneration , threadGeneration);
|
|
||||||
// If an object hasn't been visited and we have scanned everywhere
|
|
||||||
// since we cleared its visited flag, delete it. This works
|
|
||||||
// because the heap write barrier sets the visited flag.
|
|
||||||
if (!GCTestFlag(object->pointer, GCFlagVisited) &&
|
|
||||||
object->visitClearedGeneration < threadGeneration)
|
|
||||||
{
|
|
||||||
GCFreeObjectUnsafe(object->pointer);
|
|
||||||
traced_object_remove(traced_objects, object->pointer);
|
|
||||||
}
|
|
||||||
} while (oldPtr != ((object = traced_object_current(traced_objects, &e)))
|
|
||||||
&& object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCRunTracerIfNeeded(BOOL forceCollect)
|
|
||||||
{
|
|
||||||
struct traced_object_table_enumerator *e = NULL;
|
|
||||||
GCTracedPointer *ptr;
|
|
||||||
// See if we can avoid running the tracer.
|
|
||||||
while ((ptr = traced_object_next(traced_objects, &e)))
|
|
||||||
{
|
|
||||||
id object = ptr->pointer;
|
|
||||||
// Throw away any objects that are referenced by the heap
|
|
||||||
if (GCGetRetainCount(object) > 0 &&
|
|
||||||
GCColourOfObject(object) != GCColourRed)
|
|
||||||
{
|
|
||||||
// Make sure that the retain count is still > 0. If not then it
|
|
||||||
// may have been released but not added to the tracing list
|
|
||||||
// (because it was marked for tracing already)
|
|
||||||
if (GCGetRetainCount(object) > 0 &&
|
|
||||||
GCColourOfObject(object) != GCColourRed)
|
|
||||||
{
|
|
||||||
pthread_rwlock_wrlock(&traced_objects_lock);
|
|
||||||
traced_object_remove(traced_objects, object);
|
|
||||||
pthread_rwlock_unlock(&traced_objects_lock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (GCTestFlag(object, GCFlagVisited))
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Clearing visited flag for %x\n", (int)object);
|
|
||||||
GCClearFlag(object, GCFlagVisited);
|
|
||||||
ptr->visitClearedGeneration = GCGeneration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//fprintf(stderr, "Incrementing generation\n");
|
|
||||||
// Invalidate all stack scans.
|
|
||||||
GCGeneration++;
|
|
||||||
// Only actually run the tracer if we have more than a few objects that
|
|
||||||
// might need freeing. No point killing the cache just to reclaim one or
|
|
||||||
// two objects...
|
|
||||||
if (traced_objects->table_used > 256 || forceCollect)
|
|
||||||
{
|
|
||||||
GCRunTracer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an object for tracing that the cycle detector has decided needs freeing.
|
|
||||||
*/
|
|
||||||
void GCAddObjectForTracing(id object)
|
|
||||||
{
|
|
||||||
if (!traced_object_table_get(traced_objects, object))
|
|
||||||
{
|
|
||||||
//fprintf(stderr, "Cycle detector nominated %x for tracing\n", (int)object);
|
|
||||||
GCTracedPointer obj = {object, 0, 0, 0};
|
|
||||||
traced_object_insert(traced_objects, obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCAddObjectsForTracing(GCThread *thr)
|
|
||||||
{
|
|
||||||
id *buffer = thr->freeBuffer;
|
|
||||||
unsigned int count = thr->freeBufferInsert;
|
|
||||||
//unsigned int generation = GCHeapScanGeneration;
|
|
||||||
// No locking is needed for this table, because it is always accessed from
|
|
||||||
// the same thread
|
|
||||||
if (NULL == thr->unescapedObjects)
|
|
||||||
{
|
|
||||||
thr->unescapedObjects = unescaped_object_create(256);
|
|
||||||
}
|
|
||||||
unescaped_object_table *unescaped = thr->unescapedObjects;
|
|
||||||
|
|
||||||
pthread_rwlock_wrlock(&traced_objects_lock);
|
|
||||||
for (unsigned int i=0 ; i<count ; i++)
|
|
||||||
{
|
|
||||||
id object = buffer[i];
|
|
||||||
if (!GCObjectIsDynamic(object))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Skip objects that have a strong retain count > 0. They are
|
|
||||||
// definitely still referenced...
|
|
||||||
if (GCGetRetainCount(object) > 0 &&
|
|
||||||
GCColourOfObject(object) != GCColourRed)
|
|
||||||
{
|
|
||||||
// ...but they might have become part of a cycle
|
|
||||||
GCSetFlag(object, GCFlagBuffered);
|
|
||||||
GCSetColourOfObject(object, GCColourPurple);
|
|
||||||
GCScanForCycles(&object, 1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (GCTestFlag(object, GCFlagEscaped))
|
|
||||||
{
|
|
||||||
// FIXME: Check if the object is already there, don't add it again
|
|
||||||
// if it is, but do update its generation. It was seen by
|
|
||||||
// something in this thread, so it might still be on the stack
|
|
||||||
// here, or have been moved to the heap.
|
|
||||||
if (!traced_object_table_get(traced_objects, object))
|
|
||||||
{
|
|
||||||
GCTracedPointer obj = {object, 0, 0, 0};
|
|
||||||
traced_object_insert(traced_objects, obj);
|
|
||||||
// Make sure that this object is not in the thread's list as well.
|
|
||||||
unescaped_object_remove(unescaped, object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!unescaped_object_table_get(unescaped, object))
|
|
||||||
{
|
|
||||||
unescaped_object_insert(unescaped, object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_rwlock_unlock(&traced_objects_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void GCAddBufferForTracingTrampoline(void *b)
|
|
||||||
{
|
|
||||||
struct gc_buffer_header *buffer = b;
|
|
||||||
GCTracedRegion region = { buffer, (char*)buffer + sizeof(struct gc_buffer_header),
|
|
||||||
(char*)buffer + sizeof(struct gc_buffer_header) + buffer->size };
|
|
||||||
fprintf(stderr, "Buffer has size %d (%d)\n", buffer->size, (int)region.end - (int)region.start);
|
|
||||||
GCTracedRegionInsert(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCAddBufferForTracing(struct gc_buffer_header *buffer)
|
|
||||||
{
|
|
||||||
GCPerform(GCAddBufferForTracingTrampoline, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: memmove_collectable does this for a whole region, but only does the
|
|
||||||
// locking once.
|
|
||||||
id objc_assign_strongCast(id obj, id *ptr)
|
|
||||||
{
|
|
||||||
BOOL objIsDynamic = GCObjectIsDynamic(obj);
|
|
||||||
// This object is definitely stored somewhere, so mark it as visited
|
|
||||||
// for now.
|
|
||||||
if (objIsDynamic && obj)
|
|
||||||
{
|
|
||||||
GCSetFlag(obj, GCFlagVisited);
|
|
||||||
// Tracing semantics do not apply to objects with CF semantics, so skip the
|
|
||||||
// next bits if the CF flag is set.
|
|
||||||
if (obj && !GCTestFlag(obj, GCFlagCFObject))
|
|
||||||
{
|
|
||||||
// Don't free this just after scanning the stack.
|
|
||||||
GCSetFlag(obj, GCFlagEscaped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_rwlock_wrlock(&traced_objects_lock);
|
|
||||||
GCTracedPointer *old = traced_object_table_get(traced_objects, *ptr);
|
|
||||||
if (old)
|
|
||||||
{
|
|
||||||
// If the value that we are overwriting is a traced pointer and this is
|
|
||||||
// the pointer to it that we are tracking then mark it as not visited.
|
|
||||||
//
|
|
||||||
// This object may still have been copied to a stack. If it hasn't
|
|
||||||
// been copied to this stack, then we can collect it in future if it
|
|
||||||
// isn't in any other heap blocks?
|
|
||||||
if (old->heapAddress == ptr)
|
|
||||||
{
|
|
||||||
old->heapAddress = 0;
|
|
||||||
old->visitClearedGeneration = GCGeneration + 1;
|
|
||||||
GCClearFlag(*ptr, GCFlagVisited);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (objIsDynamic && obj)
|
|
||||||
{
|
|
||||||
GCTracedPointer *new = traced_object_table_get(traced_objects, obj);
|
|
||||||
if (new)
|
|
||||||
{
|
|
||||||
new->heapAddress = ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_rwlock_unlock(&traced_objects_lock);
|
|
||||||
*ptr = obj;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* Visitor function. Visiting is non-recursive. You must call
|
|
||||||
* GCVisitChildren() on the object argument if you wish to explore the entire
|
|
||||||
* graph.
|
|
||||||
*/
|
|
||||||
typedef void (*visit_function_t)(id object, void *context, BOOL isWeak);
|
|
||||||
|
|
||||||
void GCVisitChildren(id object, visit_function_t function, void *argument,
|
|
||||||
BOOL visitWeakChildren);
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
#include "../objc/objc-api.h"
|
|
||||||
#include "../objc/runtime.h"
|
|
||||||
#import "visit.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure storing information about object children.
|
|
||||||
*
|
|
||||||
* Note: This structure is quite inefficient. We can optimise it a lot later,
|
|
||||||
* if required.
|
|
||||||
*/
|
|
||||||
struct GCChildInfo
|
|
||||||
{
|
|
||||||
/** Number of children of this class. */
|
|
||||||
unsigned int count;
|
|
||||||
/** Offsets of children. */
|
|
||||||
size_t *offsets;
|
|
||||||
/** Method pointer for enumerating extra children. */
|
|
||||||
IMP extraChildren;
|
|
||||||
};
|
|
||||||
|
|
||||||
@interface NSObject
|
|
||||||
- (BOOL)instancesRespondToSelector: (SEL)aSel;
|
|
||||||
- (IMP)instanceMethodForSelector: (SEL)aSel;
|
|
||||||
- (void)_visitChildrenWithFunction: (visit_function_t)function
|
|
||||||
context: (void*)context
|
|
||||||
visitWeak: (BOOL)aFlag;
|
|
||||||
@end
|
|
||||||
static SEL visitSelector = @selector(_visitChildrenWithFunction:context:visitWeak:);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Macro for adding an offset to the offset buffer and resizing it if required.
|
|
||||||
*/
|
|
||||||
#define ADD_OFFSET(offset) \
|
|
||||||
do {\
|
|
||||||
if (found == space)\
|
|
||||||
{\
|
|
||||||
space *= 2;\
|
|
||||||
buffer = realloc(buffer, sizeof(size_t[space]));\
|
|
||||||
}\
|
|
||||||
buffer[found++] = offset;\
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
// Note: If we want to save space we could use char*s and short*s for objects
|
|
||||||
// less than 2^8 and 2^16 big and add a header indicating this.
|
|
||||||
/**
|
|
||||||
* Create an instance variable map for the specified class. Inspects the ivars
|
|
||||||
* metadata and creates a GCChildInfo structure for the class. This is cached
|
|
||||||
* in the gc_object_type field in the class structure.
|
|
||||||
*
|
|
||||||
* FIXME: This is a hack. The compiler should generate this stuff, not the
|
|
||||||
* runtime.
|
|
||||||
*/
|
|
||||||
struct GCChildInfo *GCMakeIVarMap(Class aClass)
|
|
||||||
{
|
|
||||||
struct GCChildInfo *info = calloc(1, sizeof(struct GCChildInfo));
|
|
||||||
|
|
||||||
unsigned int ivarCount;
|
|
||||||
Ivar *ivars = class_copyIvarList(aClass, &ivarCount);
|
|
||||||
|
|
||||||
if (0 == ivarCount)
|
|
||||||
{
|
|
||||||
info->count = 0;
|
|
||||||
info->offsets = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unsigned found = 0;
|
|
||||||
// First guess - every instance variable is an object
|
|
||||||
size_t space = sizeof(size_t[ivarCount]);
|
|
||||||
size_t *buffer = malloc(space);
|
|
||||||
|
|
||||||
for (unsigned i=0 ; i<ivarCount ; ++i)
|
|
||||||
{
|
|
||||||
Ivar ivar = ivars[i];
|
|
||||||
const char *type = ivar_getTypeEncoding(ivar);
|
|
||||||
switch(type[0])
|
|
||||||
{
|
|
||||||
case '@':
|
|
||||||
{
|
|
||||||
// If it's an object, add it to the list.
|
|
||||||
// FIXME: Weak ivars
|
|
||||||
ADD_OFFSET(ivar_getOffset(ivar));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case '[':
|
|
||||||
case '{':
|
|
||||||
{
|
|
||||||
if (strchr(type, '@'))
|
|
||||||
{
|
|
||||||
//FIXME: Parse structures and arrays correctly
|
|
||||||
fprintf(stderr, "Compound type found in class %s, type: %s is "
|
|
||||||
"incorrectly handled", class_getName(aClass),
|
|
||||||
ivar_getTypeEncoding(ivar));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info->count = found;
|
|
||||||
info->offsets = realloc(buffer, sizeof(size_t[found]));
|
|
||||||
}
|
|
||||||
/* FIXME: Use the runtime functions for this
|
|
||||||
if ([aClass instancesRespondToSelector: visitSelector])
|
|
||||||
{
|
|
||||||
info->extraChildren =
|
|
||||||
[aClass instanceMethodForSelector: visitSelector];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
aClass->gc_object_type = info;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCVisitChildren(id object, visit_function_t function, void *argument,
|
|
||||||
BOOL visitWeakChildren)
|
|
||||||
{
|
|
||||||
Class cls = object->class_pointer;
|
|
||||||
while (Nil != cls)
|
|
||||||
{
|
|
||||||
if (NULL == cls->gc_object_type)
|
|
||||||
{
|
|
||||||
// FIXME: Locking
|
|
||||||
GCMakeIVarMap(cls);
|
|
||||||
}
|
|
||||||
struct GCChildInfo *info = cls->gc_object_type;
|
|
||||||
for (unsigned i=0 ; i<info->count ; ++i)
|
|
||||||
{
|
|
||||||
id child = *(id*)(((char*)object) + info->offsets[i]);
|
|
||||||
if (child != nil)
|
|
||||||
{
|
|
||||||
BOOL isWeak = (intptr_t)child & 1;
|
|
||||||
function(object, argument, isWeak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (NULL != info->extraChildren)
|
|
||||||
{
|
|
||||||
info->extraChildren(object, visitSelector, function, argument,
|
|
||||||
visitWeakChildren);
|
|
||||||
}
|
|
||||||
cls = cls->super_class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
void GCPerformDeferred(void(*function)(void*), void *data,
|
|
||||||
int useconds);
|
|
||||||
void GCPerform(void(*function)(void*), void *data);
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
#include "../toydispatch/toydispatch.h"
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
static dispatch_queue_t gc_deferred_queue;
|
|
||||||
static dispatch_queue_t gc_queue;
|
|
||||||
|
|
||||||
__attribute__((constructor)) void static create_queue(void)
|
|
||||||
{
|
|
||||||
gc_deferred_queue = dispatch_queue_create("GCKit collection deferred queue", 0);
|
|
||||||
gc_queue = dispatch_queue_create("GCKit collection queue", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct gc_deferred_execution_t
|
|
||||||
{
|
|
||||||
useconds_t time;
|
|
||||||
dispatch_function_t function;
|
|
||||||
void *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void perform_deferred(void *c)
|
|
||||||
{
|
|
||||||
struct gc_deferred_execution_t *context = c;
|
|
||||||
usleep(context->time);
|
|
||||||
dispatch_async_f(gc_queue, context->function, context->data);
|
|
||||||
free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs function(data) at some point at least useconds microseconds in the
|
|
||||||
* future.
|
|
||||||
*/
|
|
||||||
void GCPerformDeferred(dispatch_function_t function, void *data,
|
|
||||||
int useconds)
|
|
||||||
{
|
|
||||||
struct gc_deferred_execution_t *context = malloc(sizeof(struct gc_deferred_execution_t));
|
|
||||||
context->time = useconds;
|
|
||||||
context->function = function;
|
|
||||||
context->data = data;
|
|
||||||
dispatch_async_f(gc_deferred_queue, context, perform_deferred);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GCPerform(dispatch_function_t function, void *data)
|
|
||||||
{
|
|
||||||
dispatch_async_f(gc_queue, data, function);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue