diff --git a/GCKit/COPYING b/GCKit/COPYING deleted file mode 100644 index 8647a56..0000000 --- a/GCKit/COPYING +++ /dev/null @@ -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. - diff --git a/GCKit/DESIGN b/GCKit/DESIGN deleted file mode 100644 index 1a52bcd..0000000 --- a/GCKit/DESIGN +++ /dev/null @@ -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. diff --git a/GCKit/GNUmakefile b/GCKit/GNUmakefile deleted file mode 100644 index 8361369..0000000 --- a/GCKit/GNUmakefile +++ /dev/null @@ -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 diff --git a/GCKit/NSObject.m b/GCKit/NSObject.m deleted file mode 100644 index df5928e..0000000 --- a/GCKit/NSObject.m +++ /dev/null @@ -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 diff --git a/GCKit/cycle.h b/GCKit/cycle.h deleted file mode 100644 index 0f9d30c..0000000 --- a/GCKit/cycle.h +++ /dev/null @@ -1,3 +0,0 @@ -void GCScanForCycles(id *loopBuffer, unsigned count); -id GCRetain(id anObject); -void GCRelease(id anObject); diff --git a/GCKit/cycle.m b/GCKit/cycle.m deleted file mode 100644 index 92ec8f0..0000000 --- a/GCKit/cycle.m +++ /dev/null @@ -1,253 +0,0 @@ -#include "../objc/runtime.h" -#import "object.h" -#import "malloc.h" -#import "thread.h" -#import "visit.h" -#include - -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 %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 diff --git a/GCKit/inline.m b/GCKit/inline.m deleted file mode 100644 index fd93e7e..0000000 --- a/GCKit/inline.m +++ /dev/null @@ -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" diff --git a/GCKit/malloc.h b/GCKit/malloc.h deleted file mode 100644 index 9299de8..0000000 --- a/GCKit/malloc.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * malloc.h - defines allocation and deallocation hooks and functions for GCKit. - */ -#include - -/** - * 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); diff --git a/GCKit/malloc.m b/GCKit/malloc.m deleted file mode 100644 index 832cad0..0000000 --- a/GCKit/malloc.m +++ /dev/null @@ -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 -#include - -/** - * 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); -} diff --git a/GCKit/object.h b/GCKit/object.h deleted file mode 100644 index 4ebb4a6..0000000 --- a/GCKit/object.h +++ /dev/null @@ -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); -} diff --git a/GCKit/static.h b/GCKit/static.h deleted file mode 100644 index 1e150ba..0000000 --- a/GCKit/static.h +++ /dev/null @@ -1,12 +0,0 @@ -#include - -/** - * 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); -} diff --git a/GCKit/test.m b/GCKit/test.m deleted file mode 100644 index deb02b0..0000000 --- a/GCKit/test.m +++ /dev/null @@ -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 -#include -#include - -@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); -} diff --git a/GCKit/thread.h b/GCKit/thread.h deleted file mode 100644 index 360d211..0000000 --- a/GCKit/thread.h +++ /dev/null @@ -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); diff --git a/GCKit/thread.m b/GCKit/thread.m deleted file mode 100644 index 6fa0694..0000000 --- a/GCKit/thread.m +++ /dev/null @@ -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 -#include -#include -#include - -#include -#include -/** - * 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); -} diff --git a/GCKit/trace.h b/GCKit/trace.h deleted file mode 100644 index 2e9fc7e..0000000 --- a/GCKit/trace.h +++ /dev/null @@ -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); diff --git a/GCKit/trace.m b/GCKit/trace.m deleted file mode 100644 index 381925f..0000000 --- a/GCKit/trace.m +++ /dev/null @@ -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 -#include -#include -#include -#include -#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 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; -} diff --git a/GCKit/visit.h b/GCKit/visit.h deleted file mode 100644 index 6068dc7..0000000 --- a/GCKit/visit.h +++ /dev/null @@ -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); diff --git a/GCKit/visit.m b/GCKit/visit.m deleted file mode 100644 index 8a11b6a..0000000 --- a/GCKit/visit.m +++ /dev/null @@ -1,145 +0,0 @@ -#include "../objc/objc-api.h" -#include "../objc/runtime.h" -#import "visit.h" -#include -#include -#include - - -/** - * 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 ; icount = 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 ; icount ; ++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; - } -} diff --git a/GCKit/workqueue.h b/GCKit/workqueue.h deleted file mode 100644 index d8bbe64..0000000 --- a/GCKit/workqueue.h +++ /dev/null @@ -1,4 +0,0 @@ - -void GCPerformDeferred(void(*function)(void*), void *data, - int useconds); -void GCPerform(void(*function)(void*), void *data); diff --git a/GCKit/workqueue.m b/GCKit/workqueue.m deleted file mode 100644 index e1911c7..0000000 --- a/GCKit/workqueue.m +++ /dev/null @@ -1,46 +0,0 @@ -#include "../toydispatch/toydispatch.h" -#include -#include - -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); -}