// broker.c -- the function registry and the uniform call entry point. // // The registry is an open-addressing hash table (power-of-two slot count, linear // probing, FNV-1a hash). A native function is registered once and becomes // callable by name from every engine. calogCall is the single dispatch point; // the actor layer will later intercept it to route calls whose owning context is // not the caller's, but in the core every call is invoked inline. #define _POSIX_C_SOURCE 200809L #include "calogInternal.h" #include #include #define BROKER_INITIAL_SLOTS 16 #define BROKER_LOAD_PERCENT 70 #define BROKER_PERCENT_SCALE 100 #define FNV_OFFSET_BASIS 1469598103934665603ULL #define FNV_PRIME 1099511628211ULL // probeSlot masks the hash with slotCount - 1, which is only a full bit mask // when slotCount is a power of two. Both the initial size and the growth factor // must preserve that. _Static_assert((BROKER_INITIAL_SLOTS & (BROKER_INITIAL_SLOTS - 1)) == 0, "registry slot count must be a power of two"); _Static_assert((CALOG_GROWTH_FACTOR & (CALOG_GROWTH_FACTOR - 1)) == 0, "registry growth factor must be a power of two"); static int32_t brokerGrow(CalogT *broker); static uint64_t hashName(const char *name); static CalogEntryT *probeSlot(CalogT *broker, const char *name); static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline); int32_t calogCall(CalogT *broker, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result) { CalogEntryT *entry; calogValueNil(result); entry = calogLookup(broker, name); if (entry == NULL) { return calogFail(result, calogErrNotFoundE, "no such function"); } if (broker->routeHook != NULL) { return broker->routeHook(broker, entry, args, argCount, result); } return entry->fn(args, argCount, result, entry->userData); } CalogT *calogBrokerCreate(void) { CalogT *broker; broker = (CalogT *)calloc(1, sizeof(*broker)); if (broker == NULL) { return NULL; } broker->slots = (CalogEntryT *)calloc(BROKER_INITIAL_SLOTS, sizeof(CalogEntryT)); if (broker->slots == NULL) { free(broker); return NULL; } broker->slotCount = BROKER_INITIAL_SLOTS; broker->entryCount = 0; return broker; } void calogBrokerDestroy(CalogT *broker) { int64_t index; if (broker == NULL) { return; } for (index = 0; index < broker->slotCount; index++) { free(broker->slots[index].name); } free(broker->slots); free(broker->engines); free(broker); } int32_t calogFail(CalogValueT *result, int32_t status, const char *message) { int32_t initStatus; calogValueFree(result); initStatus = calogValueString(result, message, (int64_t)strlen(message)); if (initStatus != calogOkE) { calogValueNil(result); } return status; } void calogForEach(CalogT *broker, void (*visit)(const CalogEntryT *entry, void *ud), void *ud) { int64_t index; for (index = 0; index < broker->slotCount; index++) { if (broker->slots[index].name != NULL) { visit(&broker->slots[index], ud); } } } static int32_t brokerGrow(CalogT *broker) { CalogEntryT *oldSlots; CalogEntryT *newSlots; int64_t oldCount; int64_t newCount; int64_t index; oldSlots = broker->slots; oldCount = broker->slotCount; newCount = oldCount * CALOG_GROWTH_FACTOR; newSlots = (CalogEntryT *)calloc((size_t)newCount, sizeof(CalogEntryT)); if (newSlots == NULL) { return calogErrOomE; } broker->slots = newSlots; broker->slotCount = newCount; for (index = 0; index < oldCount; index++) { CalogEntryT *target; if (oldSlots[index].name == NULL) { continue; } target = probeSlot(broker, oldSlots[index].name); *target = oldSlots[index]; } free(oldSlots); return calogOkE; } CalogEntryT *calogLookup(CalogT *broker, const char *name) { CalogEntryT *slot; slot = probeSlot(broker, name); if (slot->name == NULL) { return NULL; } return slot; } int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) { return registerEntry(broker, name, fn, userData, false); } int32_t calogRegisterInline(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) { return registerEntry(broker, name, fn, userData, true); } static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline) { CalogEntryT *slot; char *nameCopy; int32_t status; slot = probeSlot(broker, name); if (slot->name != NULL) { // Re-registration replaces the existing binding in place: allocation free, // so it never grows and never fails. slot->fn = fn; slot->userData = userData; slot->runInline = runInline; return calogOkE; } if ((broker->entryCount + 1) * BROKER_PERCENT_SCALE >= broker->slotCount * BROKER_LOAD_PERCENT) { status = brokerGrow(broker); if (status != calogOkE) { return status; } slot = probeSlot(broker, name); } nameCopy = strdup(name); if (nameCopy == NULL) { return calogErrOomE; } slot->name = nameCopy; slot->fn = fn; slot->userData = userData; slot->runInline = runInline; broker->entryCount++; return calogOkE; } static uint64_t hashName(const char *name) { uint64_t hash; size_t index; hash = FNV_OFFSET_BASIS; for (index = 0; name[index] != '\0'; index++) { hash ^= (uint64_t)(unsigned char)name[index]; hash *= FNV_PRIME; } return hash; } static CalogEntryT *probeSlot(CalogT *broker, const char *name) { uint64_t mask; uint64_t index; mask = (uint64_t)broker->slotCount - 1; index = hashName(name) & mask; while (broker->slots[index].name != NULL) { if (strcmp(broker->slots[index].name, name) == 0) { return &broker->slots[index]; } index = (index + 1) & mask; } return &broker->slots[index]; }