ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_keycache.c
Go to the documentation of this file.
1
23
24#include "ra8_keycache.h"
25
26#include <stddef.h>
27#include <stdint.h>
28#include <string.h>
29
30#include "ra8_attributes.h"
31#include "ra8_check.h"
32#include "ra8_err.h"
33
35static const char* const s_tag = "ra8_keycache";
36
49
63
84RA8_INTERNAL static uint8_t* internal_cell_ptr(const ra8_keycache_t* kc, uint32_t idx)
85{
86 return &kc->cfg.cell_mem[(size_t)idx * (size_t)kc->cfg.cell_bytes];
87}
88
109RA8_INTERNAL static uint8_t* internal_key_ptr(const ra8_keycache_t* kc, uint32_t idx)
110{
111 return &kc->cfg.key_mem[(size_t)idx * (size_t)kc->cfg.key_bytes];
112}
113
135RA8_INTERNAL static void* internal_user_ptr(const ra8_keycache_t* kc, uint32_t idx)
136{
137 if (kc->cfg.user_bytes == 0U) {
138 return nullptr;
139 }
140 return &kc->cfg.user_mem[(size_t)idx * (size_t)kc->cfg.user_bytes];
141}
142
166RA8_INTERNAL static bool internal_key_eq(const ra8_keycache_t* kc, const void* a, const void* b)
167{
168 return memcmp(a, b, (size_t)kc->cfg.key_bytes) == 0;
169}
170
193RA8_INTERNAL static uint32_t internal_fnv1a(const void* key, uint32_t key_bytes)
194{
195 const uint8_t* p = (const uint8_t*)key;
196 uint32_t h = (uint32_t)k_keycache_fnv_offset;
197 for (uint32_t i = 0U; i < key_bytes; ++i) {
198 h ^= (uint32_t)p[i];
199 h *= (uint32_t)k_keycache_fnv_prime;
200 }
201 return h;
202}
203
232RA8_INTERNAL static uint32_t internal_hash(const ra8_keycache_t* kc, const void* key)
233{
234 uint32_t raw;
235 if (kc->cfg.hash != nullptr) {
236 raw = kc->cfg.hash(key, kc->cfg.key_bytes, kc->cfg.hash_ctx);
237 } else {
238 raw = internal_fnv1a(key, kc->cfg.key_bytes);
239 }
240 return raw % kc->cfg.bucket_count;
241}
242
266RA8_INTERNAL static void
267internal_unlink(ra8_keycache_t* kc, int32_t f, int32_t* head, int32_t* tail)
268{
270 if (m[f].prev != -1) {
271 m[m[f].prev].next = m[f].next;
272 } else if (*head == f) {
273 *head = m[f].next;
274 } else {
275 /* not the head and has no prev: already detached -- nothing to do */
276 }
277 if (m[f].next != -1) {
278 m[m[f].next].prev = m[f].prev;
279 } else if (*tail == f) {
280 *tail = m[f].prev;
281 } else {
282 /* not the tail and has no next: already detached -- nothing to do */
283 }
284}
285
308RA8_INTERNAL static void
309internal_push_head(ra8_keycache_t* kc, int32_t f, int32_t* head, int32_t* tail)
310{
312 m[f].prev = -1;
313 m[f].next = *head;
314 if (*head != -1) {
315 m[*head].prev = f;
316 }
317 *head = f;
318 if (*tail == -1) {
319 *tail = f;
320 }
321}
322
343{
344 const uint32_t b = internal_hash(kc, internal_key_ptr(kc, (uint32_t)f));
345 kc->cfg.meta[f].hash_next = kc->cfg.buckets[b];
346 kc->cfg.buckets[b] = f;
347}
348
371{
372 const uint32_t b = internal_hash(kc, internal_key_ptr(kc, (uint32_t)f));
373 int32_t cur = kc->cfg.buckets[b];
374 if (cur == f) {
375 kc->cfg.buckets[b] = kc->cfg.meta[f].hash_next;
376 } else {
377 for (uint32_t guard = 0U; guard < kc->cfg.cell_count; ++guard) {
378 if (cur == -1) {
379 break;
380 }
381 if (kc->cfg.meta[cur].hash_next == f) {
382 kc->cfg.meta[cur].hash_next = kc->cfg.meta[f].hash_next;
383 break;
384 }
385 cur = kc->cfg.meta[cur].hash_next;
386 }
387 }
388 kc->cfg.meta[f].hash_next = -1;
389}
390
413RA8_INTERNAL static int32_t internal_hash_lookup(const ra8_keycache_t* kc, const void* key)
414{
415 const uint32_t b = internal_hash(kc, key);
416 int32_t cur = kc->cfg.buckets[b];
417 for (uint32_t guard = 0U; guard < kc->cfg.cell_count; ++guard) {
418 if (cur == -1) {
419 break;
420 }
421 const ra8_keycache_cell_t* m = &kc->cfg.meta[cur];
422 if (m->valid != 0U) {
423 if (internal_key_eq(kc, internal_key_ptr(kc, (uint32_t)cur), key)) {
424 return cur;
425 }
426 }
427 cur = m->hash_next;
428 }
429 return -1;
430}
431
456RA8_INTERNAL static int32_t internal_first_unpinned(const ra8_keycache_t* kc, int32_t tail)
457{
458 int32_t cur = tail;
459 for (uint32_t guard = 0U; guard < kc->cfg.cell_count; ++guard) {
460 if (cur == -1) {
461 break;
462 }
463 if (kc->cfg.meta[cur].pin_count == 0U) {
464 return cur;
465 }
466 cur = kc->cfg.meta[cur].prev;
467 }
468 return -1;
469}
470
494{
495 const int32_t pb = internal_first_unpinned(kc, kc->pb_tail);
496 if (pb != -1) {
497 return pb;
498 }
499 return internal_first_unpinned(kc, kc->pt_tail);
500}
501
524{
525 if (kc->cfg.meta[f].seg == (uint8_t)k_keycache_seg_protected) {
526 internal_unlink(kc, f, &kc->pt_head, &kc->pt_tail);
527 internal_push_head(kc, f, &kc->pt_head, &kc->pt_tail);
528 return;
529 }
530 internal_unlink(kc, f, &kc->pb_head, &kc->pb_tail);
531 if (kc->protected_count >= kc->protected_cap) {
532 const int32_t d = kc->pt_tail;
533 if (d != -1) {
534 internal_unlink(kc, d, &kc->pt_head, &kc->pt_tail);
535 kc->cfg.meta[d].seg = (uint8_t)k_keycache_seg_probation;
536 internal_push_head(kc, d, &kc->pb_head, &kc->pb_tail);
537 kc->protected_count--;
538 }
539 }
540 kc->cfg.meta[f].seg = (uint8_t)k_keycache_seg_protected;
541 internal_push_head(kc, f, &kc->pt_head, &kc->pt_tail);
542 kc->protected_count++;
543}
544
572RA8_INTERNAL static void internal_access(ra8_keycache_t* kc, int32_t f)
573{
576 } else {
577 internal_unlink(kc, f, &kc->pb_head, &kc->pb_tail);
578 internal_push_head(kc, f, &kc->pb_head, &kc->pb_tail);
579 }
580}
581
606{
607 RA8_CHECK_NULL_PTR(cfg->cell_mem, s_tag, "cell_mem must not be nullptr");
608 RA8_CHECK_NULL_PTR(cfg->key_mem, s_tag, "key_mem must not be nullptr");
609 RA8_CHECK_NULL_PTR(cfg->meta, s_tag, "meta must not be nullptr");
610 RA8_CHECK_NULL_PTR(cfg->buckets, s_tag, "buckets must not be nullptr");
611 RA8_CHECK_NULL_PTR(cfg->render, s_tag, "render must not be nullptr");
612 if (cfg->user_bytes != 0U) {
613 RA8_CHECK_NULL_PTR(cfg->user_mem, s_tag, "user_mem required when user_bytes > 0");
614 }
615 return k_ra8_ok;
616}
617
643{
644 if (cfg->cell_count == 0U) {
646 }
647 if (cfg->cell_bytes == 0U) {
649 }
650 if (cfg->key_bytes == 0U) {
652 }
653 if (cfg->bucket_count == 0U) {
655 }
656 return k_ra8_ok;
657}
658
691{
692 if (cfg->evict == k_ra8_keycache_evict_slru) {
693 if ((uint32_t)cfg->protected_pct > (uint32_t)k_keycache_percent_full) {
695 }
696 }
697 return k_ra8_ok;
698}
699
729{
730 const uint32_t pct = (cfg->protected_pct == 0U) ? (uint32_t)k_keycache_protected_pct_def
731 : (uint32_t)cfg->protected_pct;
732 return (cfg->cell_count * pct) / (uint32_t)k_keycache_percent_full;
733}
734
759{
760 (void)memset(kc, 0, sizeof(*kc));
761 kc->cfg = *cfg;
762 kc->pb_head = -1;
763 kc->pb_tail = -1;
764 kc->pt_head = -1;
765 kc->pt_tail = -1;
766 for (uint32_t b = 0U; b < cfg->bucket_count; ++b) {
767 cfg->buckets[b] = -1;
768 }
769 for (uint32_t i = 0U; i < cfg->cell_count; ++i) {
770 kc->cfg.meta[i].valid = 0U;
771 kc->cfg.meta[i].pin_count = 0U;
772 kc->cfg.meta[i].seg = (uint8_t)k_keycache_seg_probation;
773 kc->cfg.meta[i].hash_next = -1;
774 internal_push_head(kc, (int32_t)i, &kc->pb_head, &kc->pb_tail);
775 }
776}
777
779{
780 RA8_CHECK_NULL_PTR(kc, s_tag, "kc must not be nullptr");
781 RA8_CHECK_NULL_PTR(cfg, s_tag, "cfg must not be nullptr");
782 const ra8_err_t perr = internal_validate_cfg_ptrs(cfg);
783 if (perr != k_ra8_ok) {
784 return perr;
785 }
786 const ra8_err_t serr = internal_validate_cfg_sizes(cfg);
787 if (serr != k_ra8_ok) {
788 return serr;
789 }
790 const ra8_err_t verr = internal_validate_cfg_policy(cfg);
791 if (verr != k_ra8_ok) {
792 return verr;
793 }
794 internal_seed_cells(kc, cfg);
796 return k_ra8_ok;
797}
798
827internal_miss(ra8_keycache_t* kc, const void* key, ra8_keycache_view_t* out_view)
828{
829 const int32_t v = internal_pick_victim(kc);
830 if (v < 0) {
831 return k_ra8_err_no_mem;
832 }
833 ra8_keycache_cell_t* m = &kc->cfg.meta[v];
834 if (m->valid != 0U) {
836 kc->evictions++;
837 }
838 const uint8_t seg = m->seg;
840 v,
841 (seg == (uint8_t)k_keycache_seg_protected) ? &kc->pt_head : &kc->pb_head,
842 (seg == (uint8_t)k_keycache_seg_protected) ? &kc->pt_tail : &kc->pb_tail);
843 if (seg == (uint8_t)k_keycache_seg_protected) {
844 kc->protected_count--;
845 }
846 uint8_t* cell = internal_cell_ptr(kc, (uint32_t)v);
847 void* user = internal_user_ptr(kc, (uint32_t)v);
848 const ra8_err_t rerr = kc->cfg.render(kc->cfg.render_ctx, key, cell, kc->cfg.cell_bytes, user);
849 if (rerr != k_ra8_ok) {
850 m->valid = 0U;
851 m->seg = (uint8_t)k_keycache_seg_probation;
852 internal_push_head(kc, v, &kc->pb_head, &kc->pb_tail);
853 return rerr;
854 }
855 (void)memcpy(internal_key_ptr(kc, (uint32_t)v), key, (size_t)kc->cfg.key_bytes);
856 m->valid = 1U;
857 m->pin_count = 1U;
858 m->seg = (uint8_t)k_keycache_seg_probation;
860 internal_push_head(kc, v, &kc->pb_head, &kc->pb_tail);
861 *out_view = (ra8_keycache_view_t){.data = cell, .user = user};
862 return k_ra8_ok;
863}
864
866{
867 RA8_CHECK_NULL_PTR(kc, s_tag, "kc must not be nullptr");
868 RA8_CHECK_NULL_PTR(key, s_tag, "key must not be nullptr");
869 RA8_CHECK_NULL_PTR(out_view, s_tag, "out_view must not be nullptr");
870 const int32_t f = internal_hash_lookup(kc, key);
871 if (f >= 0) {
872 kc->hits++;
873 kc->cfg.meta[f].pin_count++;
874 internal_access(kc, f);
875 *out_view = (ra8_keycache_view_t){.data = internal_cell_ptr(kc, (uint32_t)f),
876 .user = internal_user_ptr(kc, (uint32_t)f)};
877 return k_ra8_ok;
878 }
879 kc->misses++;
880 return internal_miss(kc, key, out_view);
881}
882
884{
885 RA8_CHECK_NULL_PTR(kc, s_tag, "kc must not be nullptr");
886 RA8_CHECK_NULL_PTR(key, s_tag, "key must not be nullptr");
887 ra8_keycache_view_t view = {};
888 const ra8_err_t gerr = ra8_keycache_get(kc, key, &view);
889 if (gerr != k_ra8_ok) {
890 return gerr;
891 }
892 /* Drop the pin now: the cell is resident but evictable, so a wrong read-ahead
893 * guess ages out before hot data is displaced by a pinned request. */
894 return ra8_keycache_put(kc, view.data);
895}
896
898{
899 RA8_CHECK_NULL_PTR(kc, s_tag, "kc must not be nullptr");
900 RA8_CHECK_NULL_PTR(data, s_tag, "data must not be nullptr");
901 const uintptr_t base = (uintptr_t)kc->cfg.cell_mem;
902 const uintptr_t addr = (uintptr_t)data;
903 if (addr < base) {
905 }
906 const uintptr_t off = addr - base;
907 const uintptr_t span = (uintptr_t)kc->cfg.cell_count * (uintptr_t)kc->cfg.cell_bytes;
908 if (off >= span) {
910 }
911 if ((off % (uintptr_t)kc->cfg.cell_bytes) != 0U) {
913 }
914 const uint32_t idx = (uint32_t)(off / (uintptr_t)kc->cfg.cell_bytes);
915 if (kc->cfg.meta[idx].pin_count == 0U) {
917 }
918 kc->cfg.meta[idx].pin_count--;
919 return k_ra8_ok;
920}
921
923 uint32_t* out_hits,
924 uint32_t* out_misses,
925 uint32_t* out_evictions)
926{
927 RA8_CHECK_NULL_PTR(kc, s_tag, "kc must not be nullptr");
928 if (kc->cfg.cell_mem == nullptr) {
930 }
931 if (out_hits != nullptr) {
932 *out_hits = kc->hits;
933 }
934 if (out_misses != nullptr) {
935 *out_misses = kc->misses;
936 }
937 if (out_evictions != nullptr) {
938 *out_evictions = kc->evictions;
939 }
940 return k_ra8_ok;
941}
static const char * s_tag
Logging / check tag.
Definition ra8_app.c:17
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_invalid_state
Module in wrong state for requested operation.
Definition ra8_err.h:161
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
int memcmp(const void *a, const void *b, size_t n)
Compare bytes in two memory areas.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
static uint32_t internal_protected_cap(const ra8_keycache_cfg_t *cfg)
Resolve the SLRU protected-segment capacity, in cells.
static void internal_seed_cells(ra8_keycache_t *kc, const ra8_keycache_cfg_t *cfg)
Seed a validated cache: clear buckets and link every cell cold.
static void internal_access(ra8_keycache_t *kc, int32_t f)
Re-reference cell f on a hit under the configured policy.
static int32_t internal_pick_victim(const ra8_keycache_t *kc)
Select an evictable victim: probationary LRU first, then protected LRU.
static void * internal_user_ptr(const ra8_keycache_t *kc, uint32_t idx)
User-descriptor pointer for cell idx, or NULL when unused.
static uint8_t * internal_cell_ptr(const ra8_keycache_t *kc, uint32_t idx)
Cell payload pointer for cell idx.
ra8_err_t ra8_keycache_get(ra8_keycache_t *kc, const void *key, ra8_keycache_view_t *out_view)
Get (and pin) the cell for key, rendering it on a miss.
static bool internal_key_eq(const ra8_keycache_t *kc, const void *a, const void *b)
Byte-wise key equality over key_bytes.
static uint32_t internal_hash(const ra8_keycache_t *kc, const void *key)
Hash a key blob into a bucket index using the configured policy.
static void internal_hash_remove(ra8_keycache_t *kc, int32_t f)
Remove cell f from its hash bucket chain.
ra8_err_t ra8_keycache_stats(const ra8_keycache_t *kc, uint32_t *out_hits, uint32_t *out_misses, uint32_t *out_evictions)
Report the cache hit / miss / eviction counters.
static void internal_unlink(ra8_keycache_t *kc, int32_t f, int32_t *head, int32_t *tail)
Detach cell f from the recency list owned by head / tail.
static int32_t internal_hash_lookup(const ra8_keycache_t *kc, const void *key)
Find the valid cell holding key, or -1.
static uint32_t internal_fnv1a(const void *key, uint32_t key_bytes)
FNV-1a hash of a key blob (the built-in default hash).
static void internal_slru_access(ra8_keycache_t *kc, int32_t f)
SLRU re-reference: promote / refresh cell f on a hit.
ra8_err_t ra8_keycache_init(ra8_keycache_t *kc, const ra8_keycache_cfg_t *cfg)
Initialise a cache engine over caller-supplied storage.
static ra8_err_t internal_miss(ra8_keycache_t *kc, const void *key, ra8_keycache_view_t *out_view)
Handle a get miss: evict a victim, render the cell, insert + pin it.
ra8_keycache_const_t
Hashing constants and the SLRU split defaults / bounds.
@ k_keycache_percent_full
Percent denominator / max split.
@ k_keycache_protected_pct_def
Default SLRU protected share.
@ k_keycache_fnv_prime
FNV-1a 32-bit prime.
@ k_keycache_fnv_offset
FNV-1a 32-bit offset basis.
static void internal_hash_insert(ra8_keycache_t *kc, int32_t f)
Insert cell f into its hash bucket chain.
ra8_err_t ra8_keycache_put(ra8_keycache_t *kc, const uint8_t *data)
Release one pin on a cell previously returned by ra8_keycache_get.
static void internal_push_head(ra8_keycache_t *kc, int32_t f, int32_t *head, int32_t *tail)
Push cell f onto the MRU head of a recency list.
static ra8_err_t internal_validate_cfg_sizes(const ra8_keycache_cfg_t *cfg)
Validate that every config sizing field is non-zero.
static ra8_err_t internal_validate_cfg_policy(const ra8_keycache_cfg_t *cfg)
Validate the SLRU split knob when the SLRU policy is selected.
ra8_keycache_seg_t
SLRU segment tags stored in ra8_keycache_cell_t::seg.
@ k_keycache_seg_probation
Probationary segment (scan absorber).
@ k_keycache_seg_protected
Protected segment (hot working set).
static ra8_err_t internal_validate_cfg_ptrs(const ra8_keycache_cfg_t *cfg)
Validate that every required config pointer is non-NULL.
static uint8_t * internal_key_ptr(const ra8_keycache_t *kc, uint32_t idx)
Key-storage pointer for cell idx.
static int32_t internal_first_unpinned(const ra8_keycache_t *kc, int32_t tail)
Find the first unpinned cell walking from tail toward the MRU.
ra8_err_t ra8_keycache_prefetch(ra8_keycache_t *kc, const void *key)
Warm the cell for key into the cache without holding a pin.
The one reusable hash + pin + evict cache engine (#147, #345).
@ k_ra8_keycache_evict_slru
Segmented LRU / 2Q (scan-resistant).
Per-cell link metadata (one caller-owned array entry per cell).
int32_t next
Recency link toward LRU within the segment, or -1.
uint16_t pin_count
Outstanding pins (0 => evictable).
int32_t prev
Recency link toward MRU within the segment, or -1.
uint8_t valid
1 => this cell holds an entry.
uint8_t seg
SLRU segment tag (probationary / protected).
int32_t hash_next
Hash bucket chain link, or -1.
Caller-supplied storage + policy + renderer for ra8_keycache_init.
void * hash_ctx
Opaque context passed to hash.
uint32_t bucket_count
Number of hash buckets (>= 1).
uint32_t user_bytes
Bytes per user descriptor (may be 0).
uint8_t * cell_mem
cell_count * cell_bytes of cell storage.
uint8_t protected_pct
SLRU protected share 1..100; 0 => 75%.
uint32_t cell_bytes
Bytes per cell (the rendered payload).
uint8_t * key_mem
cell_count * key_bytes of key storage.
int32_t * buckets
bucket_count hash-bucket heads.
ra8_keycache_evict_t evict
Eviction policy (0 => LRU; SLRU opt-in).
uint32_t key_bytes
Bytes per key (>= 1).
ra8_keycache_cell_t * meta
cell_count link-metadata entries.
uint32_t cell_count
Number of cells.
void * render_ctx
Opaque context passed to render.
ra8_keycache_render_fn render
Render-on-miss callback.
uint8_t * user_mem
cell_count * user_bytes, or NULL if none.
ra8_keycache_hash_fn hash
Key hash; NULL selects built-in FNV-1a.
Cache engine state (caller-owned; treat as private).
uint32_t protected_count
Cells in the protected segment.
int32_t pt_tail
Protected LRU cell, or -1 (SLRU only).
uint32_t protected_cap
Protected-segment capacity (0 = LRU).
int32_t pt_head
Protected MRU cell, or -1 (SLRU only).
uint32_t hits
Get hits so far.
uint32_t misses
Get misses so far.
ra8_keycache_cfg_t cfg
Configuration (copied at init).
uint32_t evictions
Entries evicted so far.
int32_t pb_tail
Probationary LRU cell, or -1.
int32_t pb_head
Probationary MRU cell, or -1.
A pinned view of a cached cell returned by ra8_keycache_get.
uint8_t * data
Cell payload (cell_bytes wide).