Skip to content

Instantly share code, notes, and snippets.

@nwellnhof
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nwellnhof/8daf2871fda012fc23c6 to your computer and use it in GitHub Desktop.
Save nwellnhof/8daf2871fda012fc23c6 to your computer and use it in GitHub Desktop.
// 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