Last active
August 29, 2015 14:23
-
-
Save nwellnhof/8daf2871fda012fc23c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a sketch of how to make caching of Ruby objects work with | |
// Ruby's GC under Clownfish. We use a registry that keeps a list of all | |
// Ruby objects created from Clownfish. This list is walked during GC to | |
// mark all objects with a positive Clownfish refcount and prevent them | |
// from being destroyed if all references from Ruby space were dropped. | |
// | |
// Once a Clownfish object has been passed to Ruby, it lives on even with | |
// a zero Clownfish refcount. This means that there are no more references | |
// from Clownfish but possibly from Ruby space. The Clownfish refcount | |
// might even become positive again if an object is passed back to | |
// Clownfish from Ruby. | |
// | |
// The registry uses a free list to add and remove values in amortized | |
// constant time. This makes it necessary to store a registry index in | |
// cfish_Obj instead of a Ruby VALUE, resulting in an additional | |
// indirection. This also means that the registry can't be compacted. | |
// | |
// Free list nodes use the LSB as marker. | |
typedef struct { | |
size_t ruby_reg_index; // Used to lookup cached Ruby object. | |
size_t ref_count; | |
Class *klass; | |
} Obj; | |
typedef union { | |
VALUE ruby_value; | |
size_t free_list_node; | |
} RubyRegEntry; | |
typedef struct { | |
RubyRegEntry *entries; | |
size_t free_list_head; // Index, not a node. | |
size_t size; | |
} RubyReg; | |
static RubyReg *ruby_reg; | |
void* | |
Obj_To_Host(Obj *self) { | |
VALUE v; | |
if (self->ruby_reg_index) { | |
v = ruby_reg->entries[self->ruby_reg_index].ruby_value; | |
} | |
else { | |
VALUE ruby_class = Class_find_ruby_class(self->klass); | |
v = Data_Wrap_Struct(ruby_class, NULL, Obj_ruby_gc_free, self); | |
self->ruby_reg_index = RubyReg_add(ruby_reg, v); | |
} | |
return (void*)v; | |
} | |
void | |
Obj_dec_refcnt(Obj *self) { | |
if (--self->ref_count == 0 && !self->ruby_reg_index) { | |
// Object was never passed to Ruby. | |
Obj_Destroy(self); | |
} | |
} | |
void | |
Obj_ruby_gc_free(void *arg) { | |
Obj *self = (Obj*)arg; | |
RubyReg_remove(ruby_reg, self->ruby_reg_index); | |
Obj_Destroy(self); | |
} | |
void | |
RubyReg_ruby_gc_mark(void *arg) { | |
RubyReg *self = (RubyReg*)arg; | |
for (size_t i = 0; i < self->size; i++) { | |
RubyRegEntry entry = self->entries[i]; | |
if (S_on_free_list(entry)) { continue; } | |
VALUE v = entry.ruby_value; | |
Obj *obj = (Obj*)DATA_PTR(v); | |
if (obj->ref_count > 0) { | |
// Only mark objects with a positive refcount. | |
rb_gc_mark(v); | |
} | |
} | |
} | |
RubyReg* | |
RubyReg_new(size_t size) { | |
RubyReg *self = (RubyReg*)malloc(sizeof(RubyReg)); | |
RubyRegEntry *entries = (RubyRegEntry*)malloc(size * sizeof(RubyRegEntry)); | |
entries[0] = 0; // Unused | |
// Initialize free list. | |
for (size_t i = 1; i < size - 1; i++) { | |
entries[i].free_list_node = S_make_free_list_node(i + 1); | |
} | |
entries[size-1] = S_make_free_list_node(0); | |
self->entries = entries; | |
self->free_list_head = 1; | |
self->size = size; | |
return self; | |
} | |
size_t | |
RubyReg_add(RubyReg *self, VALUE v) { | |
if (!self->free_list_head) { | |
RubyReg_grow(self); | |
} | |
size_t index = self->free_list_head; | |
RubyRegEntry *entry = &self->entries[index]; | |
self->free_list_head = S_free_list_index(entry->free_list_node); | |
entry->ruby_val = v; | |
return index; | |
} | |
RubyReg_remove(RubyReg *self, size_t index) { | |
self->entries[index].free_list_node | |
= S_make_free_list_node(self->free_list_head); | |
self->free_list_head = index; | |
} | |
static inline size_t | |
S_make_free_list_node(size_t index) { | |
return (index << 1) | 1; | |
} | |
static inline bool | |
S_on_free_list(Entry entry) { | |
return entry.free_list_node & 1; | |
} | |
static inline size_t | |
S_free_list_index(size_t node) { | |
return node >> 1; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment