ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_vmem.h File Reference

Byte-range page cache with SLRU eviction (Layer 2, #147). More...

#include <stdint.h>
#include "ra8_err.h"
#include "ra8_keycache.h"
Include dependency graph for ra8_vmem.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  ra8_vmem_key_t
 The (object_id, frame-aligned offset) key the page cache hashes on. More...
struct  ra8_vmem_cfg_t
 Caller-supplied storage + loader for ra8_vmem_init. More...
struct  ra8_vmem_t
 Page-cache state (caller-owned; treat as private). More...

Typedefs

typedef ra8_err_t(* ra8_vmem_loader_fn) (void *ctx, uint32_t object_id, uint64_t offset, uint8_t *frame, uint32_t frame_bytes)
 Fill a frame with one page of an object (the storage DIP seam).
typedef ra8_keycache_cell_t ra8_vmem_frame_t
 Per-frame cache metadata (one caller-owned array entry per frame).

Functions

ra8_err_t ra8_vmem_init (ra8_vmem_t *vm, const ra8_vmem_cfg_t *cfg)
 Initialise a page cache over caller-supplied storage.
ra8_err_t ra8_vmem_get (ra8_vmem_t *vm, uint32_t object_id, uint64_t offset, void **out_page)
 Get (and pin) the frame holding object object_id at offset.
ra8_err_t ra8_vmem_put (ra8_vmem_t *vm, void *page)
 Release one pin on a frame previously returned by ra8_vmem_get.
ra8_err_t ra8_vmem_prefetch (ra8_vmem_t *vm, uint32_t object_id, uint64_t offset)
 Warm the frame holding object object_id at offset into the cache without holding a pin (single-threaded read-ahead / prefetch).
ra8_err_t ra8_vmem_stats (const ra8_vmem_t *vm, uint32_t *out_hits, uint32_t *out_misses, uint32_t *out_evictions)
 Report the cache hit / miss / eviction counters.

Detailed Description

Byte-range page cache with SLRU eviction (Layer 2, #147).

Tag
[Ring 2 / Core] {World: NS}

The heart of the #147 memory hierarchy: a software page cache that lets the reader touch objects far larger than RAM (GB-class EPUB/CBZ) while the resident set stays bounded by a fixed frame budget, independent of file size. There is no MMU, so access is handle-basedra8_vmem_get(object_id, offset) returns a pointer to a pinned frame holding that page; a miss evicts an unpinned victim, loads the page through a caller-supplied loader, and inserts it. The caller ra8_vmem_puts the frame when done.

A typed facade over the one cache engine (#345)

ra8_vmem no longer carries its own hash/pin/evict machinery. It is a thin typed facade over ::ra8_keycache, the single reader cache engine, configured for the SLRU policy with an (object_id, frame-aligned offset) key: the byte page is the cell payload, the ra8_vmem_loader_fn adapts to the engine's render-on-miss seam, and the page-number hash is injected. The image-tile and glyph caches are the LRU facades over the same engine. What ra8_vmem adds on top is this pointer-handle API, the frame-boundary alignment, and the ra8_vmem_prefetch read-ahead helper – the representation its byte-range consumers want and the tile/glyph consumers do not.

Eviction is SLRU (Segmented LRU / 2Q) – the policy chosen in #147 because it is the only scan-resistant policy with deterministic O(1) (WCET = 1) victim selection: a probationary segment absorbs one-shot linear scans while a protected segment holds the re-referenced working set, so a page-turn flood does not evict the hot set. The victim is always the probationary LRU (then the protected LRU); pinned frames are skipped.

Tuning the protected/probationary split (cfg.protected_pct)

The one policy knob is the share of frames the protected segment may hold; the rest is the probationary scan buffer. It is a per-cache tuning axis (#232) because the sweet spot depends on the workload's hot-set size versus its scan volume:

  • Default (protected_pct == 0 selects 75%). Good general default: most of the cache protects re-referenced data and a quarter absorbs scans.
  • Manga / longstrip streaming (a page-turn flood over a >SDRAM book). The reader scrolls forward through thousands of never-revisited band pages (a huge one-shot scan) while a small hot set – the archive central directory, the current chapter's tile index, the JOF atlas headers/footers re-read on every page open – is touched repeatedly. Size protected_pct to comfortably cover that hot metadata set (so it is never demoted by the flood) while leaving the remaining frames as a large probationary buffer that both soaks the scan and keeps a short back-flip (scroll-up) window resident. Under-sizing the protected segment lets the flood evict metadata that must then be re-paged on the next page open; over-sizing it shrinks the back-flip window. The resident set stays bounded by frame_count at every split – only the hit rate moves.

The split changes which frame is evicted, never how many are resident: the bounded-RAM guarantee (residency <= frame_count) is independent of the knob.

Zero allocation (NASA P10 Rule 3): the caller provides the frame storage, the per-frame metadata array, the key-storage array, and the hash-bucket array – all carved once from a tier arena (::ra8_arena) at init. The frames themselves come from a tier's ::ra8_slab in production, but ra8_vmem only needs a contiguous frame region.

Note
Not thread-safe; the reader is single-threaded (or serialises access).
Since
0.1.0

Definition in file ra8_vmem.h.

Typedef Documentation

◆ ra8_vmem_frame_t

Per-frame cache metadata (one caller-owned array entry per frame).

Treat as private; the cache owns the contents. Since #345 folded the cache machinery into ::ra8_keycache, this is exactly an ra8_keycache_cell_t – allocate an array of frame_count of them alongside the frame storage, the key array, and the hash buckets.

Since
0.1.0

Definition at line 122 of file ra8_vmem.h.

◆ ra8_vmem_loader_fn

typedef ra8_err_t(* ra8_vmem_loader_fn) (void *ctx, uint32_t object_id, uint64_t offset, uint8_t *frame, uint32_t frame_bytes)

Fill a frame with one page of an object (the storage DIP seam).

Called on a cache miss to populate frame with frame_bytes of object object_id starting at byte offset (a frame-aligned offset). Layer 1 (SD-paged / OSPI-XIP backends) implements this; tests use a synthetic generator.

Parameters
[in]ctxOpaque loader context (loader_ctx from the config).
[in]object_idObject being paged in.
[in]offsetFrame-aligned byte offset within the object.
[out]frameDestination frame (frame_bytes writable).
[in]frame_bytesFrame size in bytes.
Returns
ra8_err_t k_ra8_ok on success; any error aborts the get with that code.
Since
0.1.0

Definition at line 105 of file ra8_vmem.h.

Function Documentation

◆ ra8_vmem_get()

ra8_err_t ra8_vmem_get ( ra8_vmem_t * vm,
uint32_t object_id,
uint64_t offset,
void ** out_page )
nodiscard

Get (and pin) the frame holding object object_id at offset.

On a hit the frame is re-referenced (SLRU promote) and pinned. On a miss an unpinned victim is evicted (SLRU: probationary LRU first), the page is loaded through the configured loader, inserted, and pinned. The returned pointer stays valid until the matching ra8_vmem_put.

Parameters
[in]vmInitialised cache.
[in]object_idObject to page in.
[in]offsetByte offset; rounded down to a frame boundary internally.
[out]out_pageReceives the pinned frame pointer (frame_bytes wide).
Returns
ra8_err_t Error code.
Return values
k_ra8_okPage resident and pinned; *out_page set.
k_ra8_err_null_ptrvm or out_page was NULL.
k_ra8_err_no_memEvery frame is pinned (cannot evict for the miss).
k_ra8_err_*The loader failed (returned verbatim).
Precondition
vm was populated by ra8_vmem_init.
The caller will ra8_vmem_put the returned frame.
Postcondition
On success the frame's pin_count increased by one.
On any non-ok return no new pin is held.
Note
Not thread-safe.
Since
0.1.0

Definition at line 162 of file ra8_vmem.c.

References ra8_vmem_t::cfg, ra8_vmem_cfg_t::frame_bytes, k_ra8_ok, ra8_vmem_t::kc, ra8_vmem_key_t::object_id, ra8_vmem_key_t::offset, RA8_CHECK_NULL_PTR, ra8_keycache_get(), and s_tag.

Referenced by internal_book_src_read_paged(), internal_drive(), internal_touch(), ra8_vmem_prefetch(), and ra8_vmem_stream_read().

◆ ra8_vmem_init()

ra8_err_t ra8_vmem_init ( ra8_vmem_t * vm,
const ra8_vmem_cfg_t * cfg )
nodiscard

Initialise a page cache over caller-supplied storage.

Parameters
[out]vmCache state to populate (zero-initialised by the caller).
[in]cfgStorage + loader configuration (see ra8_vmem_cfg_t).
Returns
ra8_err_t Error code.
Return values
k_ra8_okCache ready; all frames cold/free.
k_ra8_err_null_ptrvm, cfg, or a required cfg pointer is NULL.
k_ra8_err_invalid_sizeframe_count, frame_bytes, or bucket_count was zero.
k_ra8_err_invalid_argcfg->protected_pct exceeds 100.
Precondition
cfg's arrays cover their declared sizes and out-live the cache.
cfg->loader is non-NULL and cfg->protected_pct <= 100.
Postcondition
On success vm is empty (0 valid frames) and the buckets are cleared.
On any non-ok return vm is left unbound.
Note
Not thread-safe.
Since
0.1.0

Definition at line 131 of file ra8_vmem.c.

References ra8_keycache_cfg_t::bucket_count, ra8_vmem_cfg_t::bucket_count, ra8_keycache_cfg_t::buckets, ra8_vmem_cfg_t::buckets, ra8_keycache_cfg_t::cell_bytes, ra8_keycache_cfg_t::cell_count, ra8_keycache_cfg_t::cell_mem, ra8_vmem_t::cfg, ra8_keycache_cfg_t::evict, ra8_vmem_cfg_t::frame_bytes, ra8_vmem_cfg_t::frame_count, ra8_vmem_cfg_t::frame_mem, ra8_keycache_cfg_t::hash, ra8_keycache_cfg_t::hash_ctx, internal_vmem_fill(), internal_vmem_hash(), k_ra8_keycache_evict_slru, k_ra8_ok, ra8_vmem_t::kc, ra8_keycache_cfg_t::key_bytes, ra8_keycache_cfg_t::key_mem, ra8_vmem_cfg_t::keys, memset(), ra8_keycache_cfg_t::meta, ra8_vmem_cfg_t::meta, ra8_keycache_t::protected_cap, ra8_vmem_t::protected_cap, ra8_keycache_cfg_t::protected_pct, ra8_vmem_cfg_t::protected_pct, RA8_CHECK_NULL_PTR, ra8_keycache_init(), ra8_keycache_cfg_t::render, ra8_keycache_cfg_t::render_ctx, s_tag, ra8_keycache_cfg_t::user_bytes, and ra8_keycache_cfg_t::user_mem.

Referenced by internal_cache_bind(), internal_cache_open(), internal_vmem_setup(), mem_run_vmem(), and sh_paged_bind().

◆ ra8_vmem_prefetch()

ra8_err_t ra8_vmem_prefetch ( ra8_vmem_t * vm,
uint32_t object_id,
uint64_t offset )
nodiscard

Warm the frame holding object object_id at offset into the cache without holding a pin (single-threaded read-ahead / prefetch).

Performs a bounded ra8_vmem_get immediately followed by ra8_vmem_put, so on return the page is resident but unpinned – it enters the SLRU/2Q probationary segment and ages out cheaply if the read-ahead guess was wrong (no prefetch backfire on a fast skim). Intended for the display-flush idle window: after rendering page N, warm page N+1 (and N-1 for back-flips) so the next page-turn tap is already resident. Best-effort – a loader failure is returned, but callers on the idle path typically discard it.

Parameters
[in]vmInitialised cache.
[in]object_idObject to warm.
[in]offsetByte offset; rounded down to a frame boundary internally.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe page is resident and left unpinned.
k_ra8_err_null_ptrvm was NULL.
k_ra8_err_no_memEvery frame is pinned (cannot evict to warm).
k_ra8_err_*The loader failed (returned verbatim; nothing warmed).
Precondition
vm was populated by ra8_vmem_init.
Called from the single owning context (e.g. the reader idle window).
Postcondition
On k_ra8_ok the page is resident with a net-zero change to pin_count.
On any non-ok return this call leaves no frame pinned.
Note
Not thread-safe. Single-threaded read-ahead only.
A warmed-but-unused frame is probationary and evicted before hot data.
See also
ra8_vmem_get() The pinning demand-page primitive this wraps.
Since
0.1.0

Definition at line 186 of file ra8_vmem.c.

References k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_vmem_get(), ra8_vmem_put(), and s_tag.

Referenced by book_src_prefetch_chapter().

◆ ra8_vmem_put()

ra8_err_t ra8_vmem_put ( ra8_vmem_t * vm,
void * page )
nodiscard

Release one pin on a frame previously returned by ra8_vmem_get.

Parameters
[in]vmInitialised cache.
[in]pageA frame pointer returned by ra8_vmem_get.
Returns
ra8_err_t Error code.
Return values
k_ra8_okPin released.
k_ra8_err_null_ptrvm or page was NULL.
k_ra8_err_invalid_argpage is not a frame of this cache, or the frame was not pinned.
Precondition
page came from ra8_vmem_get on this cache and is still pinned.
vm was populated by ra8_vmem_init.
Postcondition
On success the frame's pin_count decreased by one (evictable at zero).
On any non-ok return no state changed.
Note
Not thread-safe.
Since
0.1.0

Definition at line 179 of file ra8_vmem.c.

References ra8_vmem_t::kc, RA8_CHECK_NULL_PTR, ra8_keycache_put(), and s_tag.

Referenced by internal_book_src_read_paged(), internal_drive(), internal_touch(), ra8_vmem_prefetch(), and ra8_vmem_stream_read().

◆ ra8_vmem_stats()

ra8_err_t ra8_vmem_stats ( const ra8_vmem_t * vm,
uint32_t * out_hits,
uint32_t * out_misses,
uint32_t * out_evictions )
nodiscard

Report the cache hit / miss / eviction counters.

Parameters
[in]vmInitialised cache.
[out]out_hitsGet hits so far (may be NULL).
[out]out_missesGet misses so far (may be NULL).
[out]out_evictionsPages evicted so far (may be NULL).
Returns
ra8_err_t Error code.
Return values
k_ra8_okCounters reported.
k_ra8_err_null_ptrvm was NULL.
k_ra8_err_invalid_stateThe cache was not initialised.
Precondition
vm was populated by ra8_vmem_init.
At least one output pointer is non-NULL to be useful.
Postcondition
On success the requested counters are written.
No cache state is mutated.
Note
Thread-safe with respect to a quiescent cache (pure read).
Since
0.1.0

Definition at line 199 of file ra8_vmem.c.

References ra8_vmem_t::cfg, ra8_vmem_cfg_t::frame_mem, k_ra8_err_invalid_state, ra8_vmem_t::kc, RA8_CHECK_NULL_PTR, ra8_keycache_stats(), and s_tag.

Referenced by internal_drive(), internal_execute(), and mem_run_vmem().