Affix

 view release on metacpan or  search on metacpan

infix/src/core/cache.c  view on Meta::CPAN

/**
 * Copyright (c) 2025 Sanko Robinson
 *
 * This source code is dual-licensed under the Artistic License 2.0 or the MIT License.
 * You may choose to use this code under the terms of either license.
 *
 * SPDX-License-Identifier: (Artistic-2.0 OR MIT)
 *
 * The documentation blocks within this file are licensed under the
 * Creative Commons Attribution 4.0 International License (CC BY 4.0).
 *
 * SPDX-License-Identifier: CC-BY-4.0
 */
/**
 * @file cache.c
 * @brief Implements trampoline deduplication and caching.
 * @ingroup internal_core
 */
#include "common/infix_internals.h"
#include <stdlib.h>
#include <string.h>

/** @internal Initial number of buckets for the global trampoline cache. */
#define CACHE_BUCKETS 1021

/** @internal A single entry in the global trampoline cache. */
typedef struct _cache_entry_t {
    infix_forward_t * trampoline; /**< The cached trampoline handle. */
    struct _cache_entry_t * next; /**< Next entry in the hash bucket chain. */
} _cache_entry_t;

/** @internal The global hash table for forward trampolines. */
static _cache_entry_t * g_trampoline_cache[CACHE_BUCKETS];
/** @internal Mutex to protect the global cache. */
static infix_mutex_t g_cache_mutex = INFIX_MUTEX_INITIALIZER;

/**
 * @internal
 * @brief Computes a hash for a cache lookup.
 */
static uint64_t _cache_hash(const char * sig, void * target_fn, bool is_safe) {
    uint64_t h = 5381;
    int c;
    while ((c = *sig++))
        h = ((h << 5) + h) + c;
    h ^= (uint64_t)(uintptr_t)target_fn;
    if (is_safe)
        h ^= 0x123456789ABCDEF0ULL;
    return h;
}

/**
 * @internal
 * @brief Searches the global cache for an existing trampoline.
 * @return The cached trampoline with its ref_count incremented, or NULL if not found.
 */
infix_forward_t * _infix_cache_lookup(const char * signature, void * target_fn, bool is_safe) {
    uint64_t h = _cache_hash(signature, target_fn, is_safe);
    size_t index = h % CACHE_BUCKETS;

    INFIX_MUTEX_LOCK(&g_cache_mutex);
    for (_cache_entry_t * entry = g_trampoline_cache[index]; entry; entry = entry->next) {
        if (entry->trampoline->target_fn == target_fn && entry->trampoline->is_safe == is_safe &&
            strcmp(entry->trampoline->signature, signature) == 0) {
            entry->trampoline->ref_count++;
            INFIX_MUTEX_UNLOCK(&g_cache_mutex);
            return entry->trampoline;
        }
    }
    INFIX_MUTEX_UNLOCK(&g_cache_mutex);
    return NULL;
}

/**
 * @internal
 * @brief Inserts a trampoline into the global cache.
 */
void _infix_cache_insert(infix_forward_t * trampoline) {
    uint64_t h = _cache_hash(trampoline->signature, trampoline->target_fn, trampoline->is_safe);
    size_t index = h % CACHE_BUCKETS;

    INFIX_MUTEX_LOCK(&g_cache_mutex);
    // Double check it's not already there
    for (_cache_entry_t * entry = g_trampoline_cache[index]; entry; entry = entry->next) {
        if (entry->trampoline->target_fn == trampoline->target_fn &&
            entry->trampoline->is_safe == trampoline->is_safe &&
            strcmp(entry->trampoline->signature, trampoline->signature) == 0) {
            INFIX_MUTEX_UNLOCK(&g_cache_mutex);
            return;
        }
    }

    _cache_entry_t * entry = infix_malloc(sizeof(_cache_entry_t));
    if (!entry) {
        INFIX_MUTEX_UNLOCK(&g_cache_mutex);
        return;
    }

    entry->trampoline = trampoline;
    trampoline->ref_count++;  // Cache reference
    entry->next = g_trampoline_cache[index];
    g_trampoline_cache[index] = entry;
    INFIX_MUTEX_UNLOCK(&g_cache_mutex);
}

/**
 * @internal
 * @brief Clears all entries from the global cache.
 */
void _infix_cache_clear(void) {
    INFIX_MUTEX_LOCK(&g_cache_mutex);
    for (size_t i = 0; i < CACHE_BUCKETS; ++i) {
        _cache_entry_t * entry = g_trampoline_cache[i];
        while (entry) {
            _cache_entry_t * next = entry->next;
            if (--entry->trampoline->ref_count == 0)
                _infix_forward_destroy_internal(entry->trampoline);
            infix_free(entry);
            entry = next;
        }
        g_trampoline_cache[i] = nullptr;
    }
    INFIX_MUTEX_UNLOCK(&g_cache_mutex);
}

/**
 * @internal
 * @brief Internal non-locking removal helper.
 */
static bool _cache_remove_no_lock(infix_forward_t * trampoline) {
    if (!trampoline->signature)
        return false;
    uint64_t h = _cache_hash(trampoline->signature, trampoline->target_fn, trampoline->is_safe);
    size_t index = h % CACHE_BUCKETS;

    _cache_entry_t ** p = &g_trampoline_cache[index];
    while (*p) {
        if ((*p)->trampoline == trampoline) {
            _cache_entry_t * to_free = *p;
            *p = to_free->next;
            infix_free(to_free);
            trampoline->ref_count--;  // Decrement since cache no longer holds it
            return true;
        }
        p = &((*p)->next);
    }
    return false;
}

/**
 * @internal
 * @brief Removes a trampoline from the global cache.
 * @return True if found and removed.
 */
bool _infix_cache_remove(infix_forward_t * trampoline) {
    INFIX_MUTEX_LOCK(&g_cache_mutex);
    bool result = _cache_remove_no_lock(trampoline);
    INFIX_MUTEX_UNLOCK(&g_cache_mutex);
    return result;
}

/**
 * @internal
 * @brief Releases a reference to a trampoline, destroying it if the count hits 0.
 */
void _infix_cache_release(infix_forward_t * trampoline) {
    if (!trampoline)
        return;

    INFIX_MUTEX_LOCK(&g_cache_mutex);
    if (--trampoline->ref_count > 0) {
        INFIX_MUTEX_UNLOCK(&g_cache_mutex);
        return;
    }

    // Reference count is 0. Remove from cache and destroy.
    _cache_remove_no_lock(trampoline);
    INFIX_MUTEX_UNLOCK(&g_cache_mutex);

    _infix_forward_destroy_internal(trampoline);
}



( run in 0.589 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )