| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define C_LUCY_VTABLE |
| #define C_LUCY_OBJ |
| #define LUCY_USE_SHORT_NAMES |
| #define CHY_USE_SHORT_NAMES |
| |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include "Lucy/Object/VTable.h" |
| #include "Lucy/Object/CharBuf.h" |
| #include "Lucy/Object/Err.h" |
| #include "Lucy/Object/Hash.h" |
| #include "Lucy/Object/LockFreeRegistry.h" |
| #include "Lucy/Object/VArray.h" |
| #include "Lucy/Util/Atomic.h" |
| #include "Lucy/Util/Memory.h" |
| |
| size_t VTable_offset_of_parent = offsetof(VTable, parent); |
| |
| // Remove spaces and underscores, convert to lower case. |
| static void |
| S_scrunch_charbuf(CharBuf *source, CharBuf *target); |
| |
| LockFreeRegistry *VTable_registry = NULL; |
| |
| void |
| VTable_destroy(VTable *self) { |
| THROW(ERR, "Insane attempt to destroy VTable for class '%o'", self->name); |
| } |
| |
| VTable* |
| VTable_clone(VTable *self) { |
| VTable *twin |
| = (VTable*)Memory_wrapped_calloc(self->vt_alloc_size, 1); |
| |
| memcpy(twin, self, self->vt_alloc_size); |
| twin->name = CB_Clone(self->name); |
| twin->ref.count = 1; |
| |
| return twin; |
| } |
| |
| Obj* |
| VTable_inc_refcount(VTable *self) { |
| return (Obj*)self; |
| } |
| |
| uint32_t |
| VTable_dec_refcount(VTable *self) { |
| UNUSED_VAR(self); |
| return 1; |
| } |
| |
| uint32_t |
| VTable_get_refcount(VTable *self) { |
| UNUSED_VAR(self); |
| /* VTable_Get_RefCount() lies to other Lucy code about the refcount |
| * because we don't want to have to synchronize access to the cached host |
| * object to which we have delegated responsibility for keeping refcounts. |
| * It always returns 1 because 1 is a positive number, and thus other Lucy |
| * code will be fooled into believing it never needs to take action such |
| * as initiating a destructor. |
| * |
| * It's possible that the host has in fact increased the refcount of the |
| * cached host object if there are multiple refs to it on the other side |
| * of the Lucy/host border, but returning 1 is good enough to fool Lucy |
| * code. |
| */ |
| return 1; |
| } |
| |
| void |
| VTable_override(VTable *self, lucy_method_t method, size_t offset) { |
| union { char *char_ptr; lucy_method_t *func_ptr; } pointer; |
| pointer.char_ptr = ((char*)self) + offset; |
| pointer.func_ptr[0] = method; |
| } |
| |
| CharBuf* |
| VTable_get_name(VTable *self) { |
| return self->name; |
| } |
| |
| VTable* |
| VTable_get_parent(VTable *self) { |
| return self->parent; |
| } |
| |
| size_t |
| VTable_get_obj_alloc_size(VTable *self) { |
| return self->obj_alloc_size; |
| } |
| |
| void |
| VTable_init_registry() { |
| LockFreeRegistry *reg = LFReg_new(256); |
| if (Atomic_cas_ptr((void*volatile*)&VTable_registry, NULL, reg)) { |
| return; |
| } |
| else { |
| DECREF(reg); |
| } |
| } |
| |
| VTable* |
| VTable_singleton(const CharBuf *subclass_name, VTable *parent) { |
| if (VTable_registry == NULL) { |
| VTable_init_registry(); |
| } |
| |
| VTable *singleton = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)subclass_name); |
| if (singleton == NULL) { |
| VArray *novel_host_methods; |
| uint32_t num_novel; |
| |
| if (parent == NULL) { |
| CharBuf *parent_class = VTable_find_parent_class(subclass_name); |
| if (parent_class == NULL) { |
| THROW(ERR, "Class '%o' doesn't descend from %o", subclass_name, |
| OBJ->name); |
| } |
| else { |
| parent = VTable_singleton(parent_class, NULL); |
| DECREF(parent_class); |
| } |
| } |
| |
| // Copy source vtable. |
| singleton = VTable_Clone(parent); |
| |
| // Turn clone into child. |
| singleton->parent = parent; |
| DECREF(singleton->name); |
| singleton->name = CB_Clone(subclass_name); |
| |
| // Allow host methods to override. |
| novel_host_methods = VTable_novel_host_methods(subclass_name); |
| num_novel = VA_Get_Size(novel_host_methods); |
| if (num_novel) { |
| Hash *meths = Hash_new(num_novel); |
| uint32_t i; |
| CharBuf *scrunched = CB_new(0); |
| ZombieCharBuf *callback_name = ZCB_BLANK(); |
| for (i = 0; i < num_novel; i++) { |
| CharBuf *meth = (CharBuf*)VA_fetch(novel_host_methods, i); |
| S_scrunch_charbuf(meth, scrunched); |
| Hash_Store(meths, (Obj*)scrunched, INCREF(&EMPTY)); |
| } |
| cfish_Callback **callbacks |
| = (cfish_Callback**)singleton->callbacks; |
| for (i = 0; callbacks[i] != NULL; i++) { |
| cfish_Callback *const callback = callbacks[i]; |
| ZCB_Assign_Str(callback_name, callback->name, |
| callback->name_len); |
| S_scrunch_charbuf((CharBuf*)callback_name, scrunched); |
| if (Hash_Fetch(meths, (Obj*)scrunched)) { |
| VTable_Override(singleton, callback->func, |
| callback->offset); |
| } |
| } |
| DECREF(scrunched); |
| DECREF(meths); |
| } |
| DECREF(novel_host_methods); |
| |
| // Register the new class, both locally and with host. |
| if (VTable_add_to_registry(singleton)) { |
| // Doing this after registering is racy, but hard to fix. :( |
| VTable_register_with_host(singleton, parent); |
| } |
| else { |
| DECREF(singleton); |
| singleton = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)subclass_name); |
| if (!singleton) { |
| THROW(ERR, "Failed to either insert or fetch VTable for '%o'", |
| subclass_name); |
| } |
| } |
| } |
| |
| return singleton; |
| } |
| |
| Obj* |
| VTable_make_obj(VTable *self) { |
| Obj *obj = (Obj*)Memory_wrapped_calloc(self->obj_alloc_size, 1); |
| obj->vtable = self; |
| obj->ref.count = 1; |
| return obj; |
| } |
| |
| Obj* |
| VTable_init_obj(VTable *self, void *allocation) { |
| Obj *obj = (Obj*)allocation; |
| obj->vtable = self; |
| obj->ref.count = 1; |
| return obj; |
| } |
| |
| Obj* |
| VTable_load_obj(VTable *self, Obj *dump) { |
| Obj_load_t load = (Obj_load_t)METHOD(self, Obj, Load); |
| if (load == Obj_load) { |
| THROW(ERR, "Abstract method Load() not defined for %o", self->name); |
| } |
| Obj *invoker = VTable_Make_Obj(self); |
| Obj *loaded = load(invoker, dump); |
| DECREF(invoker); |
| return loaded; |
| } |
| |
| static void |
| S_scrunch_charbuf(CharBuf *source, CharBuf *target) { |
| ZombieCharBuf *iterator = ZCB_WRAP(source); |
| CB_Set_Size(target, 0); |
| while (ZCB_Get_Size(iterator)) { |
| uint32_t code_point = ZCB_Nip_One(iterator); |
| if (code_point > 127) { |
| THROW(ERR, "Can't fold case for %o", source); |
| } |
| else if (code_point != '_') { |
| CB_Cat_Char(target, tolower(code_point)); |
| } |
| } |
| } |
| |
| bool_t |
| VTable_add_to_registry(VTable *vtable) { |
| if (VTable_registry == NULL) { |
| VTable_init_registry(); |
| } |
| if (LFReg_Fetch(VTable_registry, (Obj*)vtable->name)) { |
| return false; |
| } |
| else { |
| CharBuf *klass = CB_Clone(vtable->name); |
| bool_t retval |
| = LFReg_Register(VTable_registry, (Obj*)klass, (Obj*)vtable); |
| DECREF(klass); |
| return retval; |
| } |
| } |
| |
| bool_t |
| VTable_add_alias_to_registry(VTable *vtable, CharBuf *alias) { |
| if (VTable_registry == NULL) { |
| VTable_init_registry(); |
| } |
| if (LFReg_Fetch(VTable_registry, (Obj*)alias)) { |
| return false; |
| } |
| else { |
| CharBuf *klass = CB_Clone(alias); |
| bool_t retval |
| = LFReg_Register(VTable_registry, (Obj*)klass, (Obj*)vtable); |
| DECREF(klass); |
| return retval; |
| } |
| } |
| |
| VTable* |
| VTable_fetch_vtable(const CharBuf *class_name) { |
| VTable *vtable = NULL; |
| if (VTable_registry != NULL) { |
| vtable = (VTable*)LFReg_Fetch(VTable_registry, (Obj*)class_name); |
| } |
| return vtable; |
| } |
| |
| |