diff --git a/Makefile b/Makefile index 30a6930..7e0946f 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,9 @@ OBJECTS = build/immix.o \ build/global_allocator.o \ build/local_allocator.o \ build/collector.o \ - build/hash.o + build/hash.o \ + build/segments.o \ + build/dynamic_loading.o all: immix.a diff --git a/include/array.h b/include/array.h index fec1bb0..11056ce 100644 --- a/include/array.h +++ b/include/array.h @@ -2,6 +2,7 @@ #define GC_ARRAY_H #include +#include // needed for perror typedef struct { long capacity; diff --git a/include/dynamic_loading.h b/include/dynamic_loading.h new file mode 100644 index 0000000..37bdbfb --- /dev/null +++ b/include/dynamic_loading.h @@ -0,0 +1,6 @@ +#ifndef GC_DYNLOAD_H +#define GC_DYNLOAD_H + +void GC_init_dyld(); + +#endif diff --git a/include/segments.h b/include/segments.h new file mode 100644 index 0000000..46bd7dc --- /dev/null +++ b/include/segments.h @@ -0,0 +1,21 @@ +#ifndef __SEGMENTS_H__ +#define __SEGMENTS_H__ + +#include "array.h" + +typedef struct GC_segment { + void *start; + void *end; + char *name; +} Segment; + +typedef void (Segments_iterator_t)(void *, Segment *); + +static Array segments; + +void Segments_init(); + +void Segments_add_segment(void* start, void* end, char *name); + +void Segments_each(void *data, Segments_iterator_t callback); +#endif diff --git a/src/collector.c b/src/collector.c index 571c6fa..ef8ed47 100644 --- a/src/collector.c +++ b/src/collector.c @@ -6,12 +6,25 @@ #include "line_header.h" #include "memory.h" #include "utils.h" +#include "segments.h" + +#ifdef DARWIN +#include "dynamic_loading.h" +#endif void GC_Collector_init(Collector *self, GlobalAllocator *allocator) { self->global_allocator = allocator; self->collect_callback = NULL; Stack_init(&self->roots, GC_getMemoryLimit()); self->is_collecting = 0; + + Segments_init(); +#ifdef DARWIN + GC_init_dyld(); +#else + Segments_add_segment(GC_DATA_START, GC_DATA_END, ".data"); + Segments_add_segment(GC_BSS_START, GC_BSS_END, ".bss"); +#endif } static inline void Collector_unmarkSmallObjects(Collector *self) { @@ -205,6 +218,12 @@ static inline void Collector_sweep(Collector *self) { //#endif } +void GC_Collector_add_segment(void *self, Segment *segment) { + DEBUG("GC: add root %p %p %s\n", segment->start, segment->end, segment->name); + + Collector_addRoots((Collector*)self, segment->start, segment->end, segment->name); +} + void GC_Collector_collect(Collector *self) { DEBUG("GC: collect start\n"); @@ -213,8 +232,7 @@ void GC_Collector_collect(Collector *self) { Collector_unmarkLargeObjects(self); // 2. collect stack roots - Collector_addRoots(self, GC_DATA_START, GC_DATA_END, ".data"); - Collector_addRoots(self, GC_BSS_START, GC_BSS_END, ".bss"); + Segments_each(self, &GC_Collector_add_segment); Collector_callCollectCallback(self); // 3. search reachable objects to mark (recursively) diff --git a/src/dynamic_loading.c b/src/dynamic_loading.c new file mode 100644 index 0000000..1f1c329 --- /dev/null +++ b/src/dynamic_loading.c @@ -0,0 +1,104 @@ +#ifdef DARWIN + +#include +#include +#include "dynamic_loading.h" +#include "segments.h" +#include "utils.h" + +/* Currently, mach-o will allow up to the max of 2^15 alignment */ +/* in an object file. */ +#ifndef L2_MAX_OFILE_ALIGNMENT +# define L2_MAX_OFILE_ALIGNMENT 15 +#endif + +/* Writable sections generally available on Darwin. */ +static const struct dyld_sections_s { + const char *seg; + const char *sect; +} GC_dyld_sections[] = { + { SEG_DATA, SECT_DATA }, + /* Used by FSF GCC, but not by OS X system tools, so far. */ + { SEG_DATA, "__static_data" }, + { SEG_DATA, SECT_BSS }, + { SEG_DATA, SECT_COMMON }, + /* FSF GCC - zero-sized object sections for targets */ + /*supporting section anchors. */ + { SEG_DATA, "__zobj_data" }, + { SEG_DATA, "__zobj_bss" } +}; + +/* Additional writable sections: */ +/* GCC on Darwin constructs aligned sections "on demand", where */ +/* the alignment size is embedded in the section name. */ +/* Furthermore, there are distinctions between sections */ +/* containing private vs. public symbols. It also constructs */ +/* sections specifically for zero-sized objects, when the */ +/* target supports section anchors. */ +static const char * const GC_dyld_add_sect_fmts[] = { + "__bss%u", + "__pu_bss%u", + "__zo_bss%u", + "__zo_pu_bss%u" +}; + +void GC_dyld_add_image(const struct mach_header64* hdr, intptr_t vmaddr_slide) { + unsigned long start, end; + unsigned i, j; + const struct section_64 *sec; + const char *name; + + for (i = 0; i < sizeof(GC_dyld_sections)/sizeof(GC_dyld_sections[0]); i++) { + sec = getsectbynamefromheader_64(hdr, GC_dyld_sections[i].seg, + GC_dyld_sections[i].sect); + if (sec == NULL || sec->size < sizeof(void*)) + continue; + start = vmaddr_slide + sec->addr; + end = start + sec->size; + Segments_add_segment((void*)start, (void*)end, GC_dyld_sections[i].sect); + } + + /* Sections constructed on demand. */ + for (j = 0; j < sizeof(GC_dyld_add_sect_fmts) / sizeof(char *); j++) { + const char *fmt = GC_dyld_add_sect_fmts[j]; + + /* Add our manufactured aligned BSS sections. */ + for (i = 0; i <= L2_MAX_OFILE_ALIGNMENT; i++) { + char secnam[16]; + + (void)snprintf(secnam, sizeof(secnam), fmt, (unsigned)i); + secnam[sizeof(secnam) - 1] = '\0'; + sec = getsectbynamefromheader_64(hdr, SEG_DATA, secnam); + if (sec == NULL || sec->size == 0) + continue; + start = vmaddr_slide + sec->addr; + end = start + sec->size; + //TODO: probably need to change fmt by secnam (but secnam is local) + Segments_add_segment(start, end, fmt); + } + } +} + +void GC_init_dyld(void) +{ + DEBUG("GC: Registering segments\n"); + /* Apple's Documentation: + When you call _dyld_register_func_for_add_image, the dynamic linker + runtime calls the specified callback (func) once for each of the images + that is currently loaded into the program. When a new image is added to + the program, your callback is called again with the mach_header for the + new image, and the virtual memory slide amount of the new image. + + This WILL properly register already linked libraries and libraries + linked in the future. + */ + _dyld_register_func_for_add_image( + (void (*)(const struct mach_header*, intptr_t))GC_dyld_add_image); + // _dyld_register_func_for_remove_image( + // (void (*)(const struct mach_header*, intptr_t))GC_dyld_image_remove); + /* Structure mach_header64 has the same fields */ + /* as mach_header except for the reserved one */ + /* at the end, so these casts are OK. */ +} + +#endif // DARWIN diff --git a/src/immix.c b/src/immix.c index df45c76..00dcb0e 100644 --- a/src/immix.c +++ b/src/immix.c @@ -53,7 +53,10 @@ void GC_init() { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // => ERRORCHECK +#if !defined(__APPLE__) // `setrobust` is part of SUSv7 and macOS only supports SUSv2 pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); +#endif + pthread_mutex_init(GC_mutex, &attr); global_allocator = malloc(sizeof(GlobalAllocator)); diff --git a/src/immix.cr b/src/immix.cr index a13060b..320ec3a 100644 --- a/src/immix.cr +++ b/src/immix.cr @@ -127,6 +127,8 @@ module GC def self.current_thread_stack_bottom {% if flag?(:linux) && flag?(:gnu) %} return {Pointer(Void).null, LibC.__libc_stack_end} + {% elsif flag?(:darwin) %} + return {Pointer(Void).null, LibC.pthread_get_stackaddr_np LibC.pthread_self} {% else %} {% raise "GC: unsupported target (only -linux-gnu is supported)" %} {% end %} diff --git a/src/lib_immix.cr b/src/lib_immix.cr index 1c59833..e12c768 100644 --- a/src/lib_immix.cr +++ b/src/lib_immix.cr @@ -28,6 +28,8 @@ lib LibC {% if flag?(:linux) && flag?(:gnu) %} $__libc_stack_end : Void* + {% elsif flag?(:darwin) %} + fun pthread_get_stackaddr_np(x0 : PthreadT) : Void* {% end %} fun abort() : Void diff --git a/src/segments.c b/src/segments.c new file mode 100644 index 0000000..c067be0 --- /dev/null +++ b/src/segments.c @@ -0,0 +1,22 @@ +#include "segments.h" +#include "utils.h" + +void Segments_init() { + Array_init(&segments, 4); +} + +void Segments_add_segment(void* start, void* end, char *name) { + DEBUG("GC: Adding segment %p, %p, %s\n", start, end, name); + Segment *segment = malloc(sizeof(Segment)); + segment->start = start; + segment->end = end; + segment->name = name; + Array_push(&segments, segment); +} + +void Segments_each(void *data, Segments_iterator_t callback) { + for (int i = 0; i < Array_size(&segments); i++) { + Segment *segment = Array_get(&segments, i); + callback(data, segment); + } +}