/* impulse_cache.c This file is part of a program that implements a Software-Defined Radio. Copyright (C) 2013, 2019, 2024 Warren Pratt, NR0V Copyright (C) 2025 Richard Samphire, MW0LGE This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The author can be reached by email at warren@wpratt.com mw0lge@grange-lane.co.uk */ // //============================================================================================// // Dual-Licensing Statement (Applies Only to Author's Contributions, Richard Samphire MW0LGE) // // ------------------------------------------------------------------------------------------ // // For any code originally written by Richard Samphire MW0LGE, or for any modifications // // made by him, the copyright holder for those portions (Richard Samphire) reserves the // // right to use, license, and distribute such code under different terms, including // // closed-source and proprietary licences, in addition to the GNU General Public License // // granted above. Nothing in this statement restricts any rights granted to recipients under // // the GNU GPL. Code contributed by others (not Richard Samphire) remains licensed under // // its original terms and is not affected by this dual-licensing statement in any way. // // Richard Samphire can be reached by email at : mw0lge@grange-lane.co.uk // //============================================================================================// #define _CRT_SECURE_NO_WARNINGS #include "comm.h" /******************************************************************************************************** * * * Impulse Cache implementation * * * ********************************************************************************************************/ #if defined(_WIN64) static const uint64_t FNV_OFFSET_BASIS_64 = 14695981039346656037ULL; // 0xcbf29ce484222325 static const uint64_t FNV_PRIME_64 = 1099511628211ULL; // 0x100000001b3 uint64_t fnv1a_hash64(const void* data, size_t len) { const uint8_t* bytes = (const uint8_t*)data; uint64_t hash = FNV_OFFSET_BASIS_64; for (size_t i = 0; i < len; ++i) { hash ^= bytes[i]; hash *= FNV_PRIME_64; } return hash; } #else static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U; // 0x811C9DC5 static const uint32_t FNV_PRIME_32 = 16777619U; // 0x01000193 uint32_t fnv1a_hash32(const void* data, size_t len) { const uint8_t* bytes = (const uint8_t*)data; uint32_t hash = FNV_OFFSET_BASIS_32; for (size_t i = 0; i < len; i++) { hash ^= bytes[i]; hash *= FNV_PRIME_32; } return hash; } #endif typedef struct _cache_entry { HASH_T hash; int N; // N complex entries in impulse. Leave as signed int as that is used everywhere double* impulse; struct _cache_entry* next; } cache_entry; static size_t _cache_counts[CACHE_BUCKETS] = { 0 }; static cache_entry* _cache_heads[CACHE_BUCKETS] = { NULL }; static CRITICAL_SECTION _cs_use_cache; static int _run = 0; static int _use_cache = 1; void remove_impulse_cache_tail(size_t bucket) { if (bucket >= CACHE_BUCKETS) return; cache_entry** pp = &_cache_heads[bucket]; while (*pp && (*pp)->next) { pp = &(*pp)->next; } if (*pp) { _aligned_free((*pp)->impulse); _aligned_free(*pp); *pp = NULL; _cache_counts[bucket]--; } } void free_impulse_cache(void) { for (size_t b = 0; b < CACHE_BUCKETS; ++b) { cache_entry* e = _cache_heads[b]; while (e) { cache_entry* next = e->next; _aligned_free(e->impulse); _aligned_free(e); e = next; } _cache_heads[b] = NULL; _cache_counts[b] = 0; } } double* get_impulse_cache_entry(size_t bucket, HASH_T hash, int N) { if (!_run) return NULL; int use; EnterCriticalSection(&_cs_use_cache); use = _use_cache; LeaveCriticalSection(&_cs_use_cache); if (!use || bucket >= CACHE_BUCKETS) return NULL; // lru, least recently used, moves cache hit to head // old cache entries will move towards the tail and eventually be dumped cache_entry* prev = NULL; cache_entry* e = _cache_heads[bucket]; while (e) { if (e->hash == hash && e->N == N) { if (prev) { prev->next = e->next; e->next = _cache_heads[bucket]; _cache_heads[bucket] = e; } double* imp = (double*) malloc0(e->N * sizeof(complex)); memcpy(imp, e->impulse, e->N * sizeof(complex)); return imp; } prev = e; e = e->next; } return NULL; } void add_impulse_to_cache(size_t bucket, HASH_T hash, int N, double* impulse) { if (!_run) return; int use; EnterCriticalSection(&_cs_use_cache); use = _use_cache; LeaveCriticalSection(&_cs_use_cache); if (!use || bucket >= CACHE_BUCKETS) return; if (_cache_counts[bucket] >= MAX_CACHE_ENTRIES) remove_impulse_cache_tail(bucket); cache_entry* e = malloc0(sizeof(cache_entry)); e->hash = hash; e->N = N; e->impulse = (double *) malloc0(N * sizeof(complex)); memcpy(e->impulse, impulse, N * sizeof(complex)); e->next = _cache_heads[bucket]; _cache_heads[bucket] = e; _cache_counts[bucket]++; } PORT int save_impulse_cache(const char* path) { if (!_run) return 0; int use; EnterCriticalSection(&_cs_use_cache); use = _use_cache; LeaveCriticalSection(&_cs_use_cache); if (!use) return 0; FILE* fp = fopen(path, "wb"); if (!fp) return -1; uint32_t buckets = CACHE_BUCKETS; if (fwrite(&buckets, sizeof(buckets), 1, fp) != 1) { fclose(fp); return -1; } for (size_t b = 0; b < CACHE_BUCKETS; b++) { uint32_t count = 0; for (cache_entry* e = _cache_heads[b]; e; e = e->next) count++; if (fwrite(&count, sizeof(count), 1, fp) != 1) { fclose(fp); return -1; } for (cache_entry* e = _cache_heads[b]; e; e = e->next) { if (fwrite(&e->hash, sizeof(HASH_T), 1, fp) != 1) { fclose(fp); return -1; } if (fwrite(&e->N, sizeof(e->N), 1, fp) != 1) { fclose(fp); return -1; } if (fwrite(e->impulse, sizeof(complex), e->N, fp) != (size_t)e->N) { fclose(fp); return -1; } } } fclose(fp); return 0; } PORT int read_impulse_cache(const char* path) { if (!_run) return 0; free_impulse_cache(); int use; EnterCriticalSection(&_cs_use_cache); use = _use_cache; LeaveCriticalSection(&_cs_use_cache); if (!use) return 0; FILE* fp = fopen(path, "rb"); if (!fp) return -1; uint32_t buckets; if (fread(&buckets, sizeof(buckets), 1, fp) != 1) { fclose(fp); return -1; } if (buckets != CACHE_BUCKETS) { fclose(fp); return -1; } for (size_t b = 0; b < buckets; b++) { uint32_t count; if (fread(&count, sizeof(count), 1, fp) != 1) { fclose(fp); return -1; } cache_entry* tail = NULL; for (uint32_t i = 0; i < count; i++) { HASH_T hash; int N; if (fread(&hash, sizeof(HASH_T), 1, fp) != 1) { fclose(fp); return -1; } if (fread(&N, sizeof(N), 1, fp) != 1) { fclose(fp); return -1; } double* data = (double*)malloc0(N * sizeof(complex)); if (fread(data, sizeof(complex), N, fp) != (size_t)N) { _aligned_free(data); fclose(fp); return -1; } cache_entry* e = (cache_entry*)malloc0(sizeof(cache_entry)); e->hash = hash; e->N = N; e->impulse = data; e->next = NULL; if (tail) tail->next = e; else _cache_heads[b] = e; tail = e; _cache_counts[b]++; } } fclose(fp); return 0; } PORT void use_impulse_cache(int use) { EnterCriticalSection(&_cs_use_cache); _use_cache = use; LeaveCriticalSection(&_cs_use_cache); } PORT void init_impulse_cache(int use) { //InitializeCriticalSection(&_cs_use_cache); InitializeCriticalSectionAndSpinCount(&_cs_use_cache, 2500); EnterCriticalSection(&_cs_use_cache); _use_cache = use; LeaveCriticalSection(&_cs_use_cache); _run = 1; } PORT void destroy_impulse_cache(void) { _run = 0; DeleteCriticalSection(&_cs_use_cache); free_impulse_cache(); }