From 06d1cc7d595b9d74f35b0f741e67e7bd7d2fe13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Costa?= Date: Thu, 24 Jul 2025 18:30:12 +0200 Subject: [PATCH] feat(cache): implement dynamic array with second chance algorithm class (#8433) --- src/misc/cache/class/lv_cache_class.h | 1 + src/misc/cache/class/lv_cache_sc_da.c | 515 ++++++++++++++++++++++++ src/misc/cache/class/lv_cache_sc_da.h | 44 ++ src/misc/cache/lv_cache.c | 3 +- src/misc/cache/lv_cache_entry.c | 40 +- src/misc/cache/lv_cache_entry_private.h | 18 +- tests/src/test_cases/cache/test_cache.c | 240 ++++++++--- 7 files changed, 792 insertions(+), 69 deletions(-) create mode 100644 src/misc/cache/class/lv_cache_sc_da.c create mode 100644 src/misc/cache/class/lv_cache_sc_da.h diff --git a/src/misc/cache/class/lv_cache_class.h b/src/misc/cache/class/lv_cache_class.h index 89b23c3f5e..a09912b14f 100644 --- a/src/misc/cache/class/lv_cache_class.h +++ b/src/misc/cache/class/lv_cache_class.h @@ -13,5 +13,6 @@ #include "lv_cache_lru_rb.h" #include "lv_cache_lru_ll.h" +#include "lv_cache_sc_da.h" #endif //LV_CACHE_CLAZZ_H diff --git a/src/misc/cache/class/lv_cache_sc_da.c b/src/misc/cache/class/lv_cache_sc_da.c new file mode 100644 index 0000000000..0dfe261ca2 --- /dev/null +++ b/src/misc/cache/class/lv_cache_sc_da.c @@ -0,0 +1,515 @@ +/** +* @file lv_cache_sc_da.c +* +*/ +/*********************************************\ +* * +* ┏ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ┓ * +* * +* ┃ Second Chance Cache ┃ * +* * +* ┃ ┌───┬───┬───┬───┬───┐ ┃ * +* │ B │ E │ A │ D │ C │ * +* ┃ ├─┬─┼─┬─┼─┬─┼─┬─┼─┬─┤ ┃ * +* │1│ │0│ │1│ │1│ │0│ │ * +* ┃ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┃ * +* [0] [1] [2] [3] [4] * +* ┃ ▲ ▲ ▲ ▲ ┃ * +* │ │ │ │ * +* ┃ │ │ │ ┌ ─ ┴ ┐ ┃ * +* │ │ │ │ add │ * +* ┃ │ │ │ │ new │ ┃ * +* │ │ │ │here │ * +* ┃ │ │ │ └ ─ ─ ┘ ┃ * +* │ │ │ * +* ┃ │ │ ┌ ─ ┴ ─ ─ ─ ─ ┐ ┃ * +* │ │ │ recently │ * +* ┃ │ │ │ used bit=1 │ ┃ * +* │ │ │ (accessed) │ * +* ┃ │ │ └ ─ ─ ─ ─ ─ ─ ┘ ┃ * +* │ │ * +* ┃ │ └ ─ ─ victim bit=0 ┃ * +* │ (will be evicted) * +* ┃ └ ─ ─ ─ ─ victim bit=1 ┃ * +* (gets second chance, * +* ┃ bit reset to 0) ┃ * +* * +* ┃ Eviction Process: ┃ * +* 1. Find first bit=0 * +* ┃ 2. If none, reset all to 0 ┃ * +* 3. Replace first entry * +* ┃ ┃ * +* ┗ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ━ ┛ * +* * +* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * +* ┃ Buffer Entry Structure ┃ * +* ┃ ┌────────────┬──────────────────────┐ ┃ * +* ┃ │ DATA │ ENTRY_DATA │ ┃ * +* ┃ │ (user type)│ (lv_cache_entry_t) │ ┃ * +* ┃ └────────────┴──────────────────────┘ ┃ * +* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * +\*********************************************/ + +/********************* + * INCLUDES + *********************/ + +#include "lv_cache_sc_da.h" +#include "../lv_cache_entry.h" +#include "../lv_cache_entry_private.h" +#include "../../../stdlib/lv_sprintf.h" +#include "../../../stdlib/lv_string.h" + +#include "../../lv_iter.h" +#include "../../lv_assert.h" +#include "../../lv_math.h" +#include "../../lv_types.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef uint32_t(get_data_size_cb_t)(const void * data); + +typedef struct { + lv_cache_t cache; + uint8_t * data; + size_t capacity; +} lv_cache_sc_da_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void * alloc_cb(void); +static bool init_cnt_cb(lv_cache_t * cache); +static void destroy_cb(lv_cache_t * cache, void * user_data); + +static lv_cache_entry_t * get_cb(lv_cache_t * cache, const void * key, + void * user_data); +static lv_cache_entry_t * add_cb(lv_cache_t * cache, const void * key, + void * user_data); +static void remove_cb(lv_cache_t * cache, lv_cache_entry_t * entry, + void * user_data); +static void drop_cb(lv_cache_t * cache, const void * key, void * user_data); +static void drop_all_cb(lv_cache_t * cache, void * user_data); +static lv_cache_entry_t * get_victim_cb(lv_cache_t * cache, void * user_data); +static lv_cache_reserve_cond_res_t reserve_cond_cb(lv_cache_t * cache, + const void * key, + size_t reserved_size, + void * user_data); + +static void * alloc_new_entry(lv_cache_sc_da_t * da, const void * key, + void * user_data); + +static lv_iter_t * cache_iter_create_cb(lv_cache_t * cache); +static lv_result_t cache_iter_next_cb(void * instance, void * context, + void * elem); + +static lv_cache_entry_t * get_possible_victim(lv_cache_sc_da_t * da, + size_t index); + +static inline void set_second_chance(lv_cache_entry_t * entry, bool value); +static inline bool has_second_chance(lv_cache_entry_t * entry); +static inline void get_entry(lv_cache_sc_da_t * da, size_t index, + void ** cache_data, lv_cache_entry_t ** cache_entry); + +/********************** + * GLOBAL VARIABLES + **********************/ +const lv_cache_class_t lv_cache_class_sc_da = { + .alloc_cb = alloc_cb, + .init_cb = init_cnt_cb, + .destroy_cb = destroy_cb, + + .get_cb = get_cb, + .add_cb = add_cb, + .remove_cb = remove_cb, + .drop_cb = drop_cb, + .drop_all_cb = drop_all_cb, + .get_victim_cb = get_victim_cb, + .reserve_cond_cb = reserve_cond_cb, + .iter_create_cb = cache_iter_create_cb, +}; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#define LV_CACHE_ENTRY_SIZE lv_cache_entry_get_size(0) + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static inline void get_entry(lv_cache_sc_da_t * da, size_t index, + void ** cache_data, lv_cache_entry_t ** cache_entry) +{ + const size_t node_size = lv_cache_entry_get_size(da->cache.node_size); + *cache_data = da->data + (index * node_size); + *cache_entry = + lv_cache_entry_get_entry(*cache_data, da->cache.node_size); +} +static inline void set_second_chance(lv_cache_entry_t * entry, bool value) +{ + if(value) { + lv_cache_entry_set_flag(entry, LV_CACHE_ENTRY_FLAG_CLASS_CUSTOM); + } + else { + lv_cache_entry_remove_flag(entry, LV_CACHE_ENTRY_FLAG_CLASS_CUSTOM); + } +} + +static inline bool has_second_chance(lv_cache_entry_t * entry) +{ + return lv_cache_entry_has_flag(entry, LV_CACHE_ENTRY_FLAG_CLASS_CUSTOM); +} + +static void * alloc_new_entry(lv_cache_sc_da_t * da, const void * key, + void * user_data) +{ + LV_UNUSED(user_data); + + LV_ASSERT_NULL(da); + LV_ASSERT_NULL(key); + + if(da == NULL || key == NULL) { + return NULL; + } + const size_t node_size = lv_cache_entry_get_size(da->cache.node_size); + + if(da->capacity == da->cache.size) { + if(da->capacity == da->cache.max_size) { + LV_LOG_ERROR( + "Reached maximum size of cache. Unable to allocate a new entry"); + return NULL; + } + + const size_t new_capacity = + LV_MIN(da->capacity == 0 ? 1 : da->capacity * 2, + da->cache.max_size); + + uint8_t * new_data = (uint8_t *)lv_realloc( + da->data, new_capacity * node_size); + + if(!new_data) { + LV_LOG_ERROR("Failed to allocate new data for cache"); + return NULL; + } + + da->data = new_data; + da->capacity = new_capacity; + } + void * last_da_entry; + lv_cache_entry_t * last_cache_entry; + + get_entry(da, da->cache.size, &last_da_entry, &last_cache_entry); + + lv_memcpy(last_da_entry, key, da->cache.node_size); + lv_cache_entry_init(last_cache_entry, &da->cache, da->cache.node_size); + lv_cache_entry_set_flag(last_cache_entry, LV_CACHE_ENTRY_FLAG_DISABLE_DELETE); + + /*New entries start with their second chance set*/ + set_second_chance(last_cache_entry, true); + return last_cache_entry; +} + +static void * alloc_cb(void) +{ + return lv_calloc(1, sizeof(lv_cache_sc_da_t)); +} + +static bool init_cnt_cb(lv_cache_t * cache) +{ + LV_ASSERT_NULL(cache); + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + return da->cache.node_size > 0 && da->cache.ops.compare_cb && + da->cache.ops.free_cb; +} + +static void destroy_cb(lv_cache_t * cache, void * user_data) +{ + LV_UNUSED(user_data); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + + if(da == NULL) { + return; + } + + cache->clz->drop_all_cb(cache, user_data); + cache->size = 0; + lv_free(da->data); + da->data = NULL; + da->capacity = 0; +} + +static lv_cache_entry_t * get_cb(lv_cache_t * cache, const void * key, + void * user_data) +{ + LV_UNUSED(user_data); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + LV_ASSERT_NULL(key); + + if(da == NULL || key == NULL) { + return NULL; + } + + /* Linear search */ + lv_cache_entry_t * cache_entry = NULL; + for(size_t i = 0; i < da->cache.size; ++i) { + void * curr_da_entry; + lv_cache_entry_t * curr_cache_entry; + get_entry(da, i, &curr_da_entry, &curr_cache_entry); + + if(da->cache.ops.compare_cb(curr_da_entry, key) == 0) { + cache_entry = curr_cache_entry; + /*When an entry is used, we set it's second chance to true again*/ + set_second_chance(cache_entry, true); + break; + } + } + return cache_entry; +} + +static lv_cache_entry_t * add_cb(lv_cache_t * cache, const void * key, + void * user_data) +{ + LV_UNUSED(user_data); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + LV_ASSERT_NULL(key); + + if(da == NULL || key == NULL) { + return NULL; + } + + lv_cache_entry_t * entry = alloc_new_entry(da, key, user_data); + if(entry == NULL) { + return NULL; + } + + cache->size += 1; + + return entry; +} + +static void remove_cb(lv_cache_t * cache, lv_cache_entry_t * entry, + void * user_data) +{ + LV_UNUSED(user_data); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + LV_ASSERT_NULL(entry); + + if(da == NULL || entry == NULL) { + return; + } + + const size_t entry_size = lv_cache_entry_get_size(da->cache.node_size); + uint8_t * da_entry_to_remove = (uint8_t *)lv_cache_entry_get_data(entry); + + void * last_da_entry; + lv_cache_entry_t * last_cache_entry; + get_entry(da, cache->size - 1, &last_da_entry, &last_cache_entry); + + if(da_entry_to_remove != last_da_entry) { + lv_memcpy(da_entry_to_remove, last_da_entry, entry_size); + } + cache->size -= 1; +} + +static void drop_cb(lv_cache_t * cache, const void * key, void * user_data) +{ + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + LV_ASSERT_NULL(key); + + if(da == NULL || key == NULL) { + return; + } + lv_cache_entry_t * entry = cache->clz->get_cb(cache, key, user_data); + if(!entry) { + return; + } + + const size_t entry_size = lv_cache_entry_get_size(da->cache.node_size); + uint8_t * da_entry_to_remove = (uint8_t *)lv_cache_entry_get_data(entry); + + void * last_da_entry; + lv_cache_entry_t * last_cache_entry; + get_entry(da, cache->size - 1, &last_da_entry, &last_cache_entry); + + if(da_entry_to_remove != last_da_entry) { + lv_memcpy(da_entry_to_remove, last_da_entry, entry_size); + } + cache->ops.free_cb(da_entry_to_remove, user_data); + cache->size -= 1; +} + +static void drop_all_cb(lv_cache_t * cache, void * user_data) +{ + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + + if(da == NULL) { + return; + } + uint32_t used_cnt = 0; + for(size_t i = 0; i < cache->size; ++i) { + void * da_entry; + lv_cache_entry_t * cache_entry; + get_entry(da, i, &da_entry, &cache_entry); + const int32_t refs = lv_cache_entry_get_ref(cache_entry); + + if(refs > 0) { + LV_LOG_WARN( + "entry (%zu) is still referenced (%" LV_PRId32 + ")", + i, refs); + used_cnt++; + continue; + } + da->cache.ops.free_cb(da_entry, user_data); + } + if(used_cnt > 0) { + LV_LOG_WARN("%" LV_PRId32 " entries are still referenced", + used_cnt); + } + + cache->size = 0; +} + +static lv_cache_entry_t * get_possible_victim(lv_cache_sc_da_t * da, size_t index) +{ + LV_ASSERT(index < da->cache.size); + + void * da_entry; + lv_cache_entry_t * cache_entry; + get_entry(da, index, &da_entry, &cache_entry); + + const uint8_t sec_chance = has_second_chance(cache_entry); + const int32_t refs = lv_cache_entry_get_ref(cache_entry); + + if(sec_chance == 0 && refs == 0) { + return cache_entry; + } + if(sec_chance == 0 && refs > 0) { + LV_LOG_INFO( + "Entry %zu should be evicted but it's still referenced %" LV_PRId32 + " times\n", + index, refs); + return NULL; + } + + /*Remove its second chance*/ + set_second_chance(cache_entry, false); + return NULL; +} + +static lv_cache_entry_t * get_victim_cb(lv_cache_t * cache, void * user_data) +{ + LV_UNUSED(user_data); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + /* + * We iterate twice to handle the complexity introduced by reference counting + * in the second chance algorithm: + * + * First iteration: Clear second chance bits and look for victims (entries with + * sec_chance=0 AND refs=0). Some entries may have sec_chance=0 but refs>0, + * making them unavailable for eviction despite being marked for removal. + * + * Second iteration: Now that all second chance bits are cleared from the first + * pass, we can find entries that are truly available for eviction (refs=0). + * We can't assume the first entry will be the victim after the first round + * because reference counts may prevent eviction of otherwise eligible entries. + * + * This ensures we give all entries a proper second chance while respecting + * active references that prevent immediate eviction. + */ + for(size_t i = 0; i < 2; ++i) { + for(size_t j = 0; j < da->cache.size; ++j) { + lv_cache_entry_t * victim = get_possible_victim(da, j); + if(victim) { + return victim; + } + } + } + return NULL; +} + +static lv_cache_reserve_cond_res_t reserve_cond_cb(lv_cache_t * cache, + const void * key, + size_t reserved_size, + void * user_data) +{ + LV_UNUSED(user_data); + LV_UNUSED(key); + + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)cache; + + LV_ASSERT_NULL(da); + + if(da == NULL) { + return LV_CACHE_RESERVE_COND_ERROR; + } + + return cache->size + reserved_size + 1 > da->cache.max_size ? + LV_CACHE_RESERVE_COND_NEED_VICTIM : + LV_CACHE_RESERVE_COND_OK; +} + +static lv_iter_t * cache_iter_create_cb(lv_cache_t * cache) +{ + return lv_iter_create(cache, lv_cache_entry_get_size(cache->node_size), + 0, cache_iter_next_cb); +} + +static lv_result_t cache_iter_next_cb(void * instance, void * context, void * elem) +{ + lv_cache_sc_da_t * da = (lv_cache_sc_da_t *)instance; + uint8_t ** da_entry = context; + LV_ASSERT_NULL(da_entry); + const size_t entry_size = lv_cache_entry_get_size(da->cache.node_size); + + if(*da_entry == NULL) { + *da_entry = da->data; + } + else { + *da_entry += entry_size; + } + + if(*da_entry == NULL) { + return LV_RESULT_INVALID; + } + + lv_memcpy(elem, da_entry, entry_size); + + return LV_RESULT_OK; +} diff --git a/src/misc/cache/class/lv_cache_sc_da.h b/src/misc/cache/class/lv_cache_sc_da.h new file mode 100644 index 0000000000..8223db729d --- /dev/null +++ b/src/misc/cache/class/lv_cache_sc_da.h @@ -0,0 +1,44 @@ +/** +* @file lv_cache_sc_da.h +* +*/ + +#ifndef LV_CACHE_SC_DA_H +#define LV_CACHE_SC_DA_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../lv_cache_private.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/************************* + * GLOBAL VARIABLES + *************************/ +LV_ATTRIBUTE_EXTERN_DATA extern const lv_cache_class_t lv_cache_class_sc_da; + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CACHE_SC_DA_H*/ diff --git a/src/misc/cache/lv_cache.c b/src/misc/cache/lv_cache.c index 33a72196f0..a029db1790 100644 --- a/src/misc/cache/lv_cache.c +++ b/src/misc/cache/lv_cache.c @@ -11,6 +11,7 @@ #include "../lv_assert.h" #include "lv_cache_entry_private.h" #include "lv_cache_private.h" +#include "../lv_profiler.h" /********************* * DEFINES @@ -319,7 +320,7 @@ static void cache_drop_internal_no_lock(lv_cache_t * cache, const void * key, vo lv_cache_entry_delete(entry); } else { - lv_cache_entry_set_invalid(entry, true); + lv_cache_entry_set_flag(entry, LV_CACHE_ENTRY_FLAG_INVALID); cache->clz->remove_cb(cache, entry, user_data); } } diff --git a/src/misc/cache/lv_cache_entry.c b/src/misc/cache/lv_cache_entry.c index 4f280e66e3..ff79d5b079 100644 --- a/src/misc/cache/lv_cache_entry.c +++ b/src/misc/cache/lv_cache_entry.c @@ -9,9 +9,7 @@ #include "lv_cache_entry.h" #include "../../stdlib/lv_sprintf.h" #include "../lv_assert.h" -#include "lv_cache.h" #include "lv_cache_entry_private.h" -#include "lv_cache_private.h" /********************* * DEFINES @@ -20,13 +18,7 @@ /********************** * TYPEDEFS **********************/ -struct _lv_cache_entry_t { - const lv_cache_t * cache; - int32_t ref_cnt; - uint32_t node_size; - bool is_invalid; -}; /********************** * STATIC PROTOTYPES **********************/ @@ -86,16 +78,10 @@ void lv_cache_entry_set_node_size(lv_cache_entry_t * entry, uint32_t node_size) entry->node_size = node_size; } -void lv_cache_entry_set_invalid(lv_cache_entry_t * entry, bool is_invalid) -{ - LV_ASSERT_NULL(entry); - entry->is_invalid = is_invalid; -} - bool lv_cache_entry_is_invalid(lv_cache_entry_t * entry) { LV_ASSERT_NULL(entry); - return entry->is_invalid; + return entry->flags & LV_CACHE_ENTRY_FLAG_INVALID; } void * lv_cache_entry_get_data(lv_cache_entry_t * entry) @@ -169,17 +155,39 @@ void lv_cache_entry_init(lv_cache_entry_t * entry, const lv_cache_t * cache, con entry->cache = cache; entry->node_size = node_size; entry->ref_cnt = 0; - entry->is_invalid = false; + entry->flags = 0; } void lv_cache_entry_delete(lv_cache_entry_t * entry) { LV_ASSERT_NULL(entry); + if(entry->flags & LV_CACHE_ENTRY_FLAG_DISABLE_DELETE) { + return; + } + void * data = lv_cache_entry_get_data(entry); lv_free(data); } +void lv_cache_entry_set_flag(lv_cache_entry_t * entry, uint8_t flags) +{ + LV_ASSERT_NULL(entry); + entry->flags |= flags; +} + +void lv_cache_entry_remove_flag(lv_cache_entry_t * entry, uint8_t flags) +{ + LV_ASSERT_NULL(entry); + entry->flags &= (~flags); +} + +bool lv_cache_entry_has_flag(lv_cache_entry_t * entry, uint8_t flags) +{ + LV_ASSERT_NULL(entry); + return (entry->flags & flags) == flags; +} + /********************** * STATIC FUNCTIONS **********************/ diff --git a/src/misc/cache/lv_cache_entry_private.h b/src/misc/cache/lv_cache_entry_private.h index 7fc2794cb8..de16ff5480 100644 --- a/src/misc/cache/lv_cache_entry_private.h +++ b/src/misc/cache/lv_cache_entry_private.h @@ -6,6 +6,7 @@ #ifndef LV_CACHE_ENTRY_PRIVATE_H #define LV_CACHE_ENTRY_PRIVATE_H +#include #ifdef __cplusplus extern "C" { #endif @@ -14,8 +15,6 @@ extern "C" { * INCLUDES *********************/ #include "../lv_types.h" -#include "../../osal/lv_os.h" -#include "../lv_profiler.h" /********************* * DEFINES @@ -25,6 +24,16 @@ extern "C" { * TYPEDEFS **********************/ +struct _lv_cache_entry_t { + const lv_cache_t * cache; + int32_t ref_cnt; + uint32_t node_size; +#define LV_CACHE_ENTRY_FLAG_INVALID (1 << 0) /** Flag indicating if the entry is invalid and can be released */ +#define LV_CACHE_ENTRY_FLAG_DISABLE_DELETE (1 << 1) /** This flag should be set if the cache class is managing the memory of the entry itself*/ +#define LV_CACHE_ENTRY_FLAG_CLASS_CUSTOM (1 << 7) /**A custom flag that can be used by the different cache classes*/ + uint8_t flags; +}; + /********************** * GLOBAL PROTOTYPES **********************/ @@ -32,11 +41,12 @@ void lv_cache_entry_reset_ref(lv_cache_entry_t * entry); void lv_cache_entry_inc_ref(lv_cache_entry_t * entry); void lv_cache_entry_dec_ref(lv_cache_entry_t * entry); void lv_cache_entry_set_node_size(lv_cache_entry_t * entry, uint32_t node_size); -void lv_cache_entry_set_invalid(lv_cache_entry_t * entry, bool is_invalid); void lv_cache_entry_set_cache(lv_cache_entry_t * entry, const lv_cache_t * cache); void * lv_cache_entry_acquire_data(lv_cache_entry_t * entry); void lv_cache_entry_release_data(lv_cache_entry_t * entry, void * user_data); - +void lv_cache_entry_set_flag(lv_cache_entry_t * entry, uint8_t flags); +void lv_cache_entry_remove_flag(lv_cache_entry_t * entry, uint8_t flags); +bool lv_cache_entry_has_flag(lv_cache_entry_t * entry, uint8_t flags); /************************* * GLOBAL VARIABLES *************************/ diff --git a/tests/src/test_cases/cache/test_cache.c b/tests/src/test_cases/cache/test_cache.c index baade8b76c..dc5357c9a9 100644 --- a/tests/src/test_cases/cache/test_cache.c +++ b/tests/src/test_cases/cache/test_cache.c @@ -3,7 +3,6 @@ #include "../lvgl.h" #include "../../lvgl_private.h" - #include "unity/unity.h" static uint32_t MEM_SIZE = 0; @@ -11,18 +10,35 @@ static uint32_t MEM_SIZE = 0; // Cache size in bytes #define CACHE_SIZE_BYTES 1000 -lv_cache_t * cache; +static lv_cache_t * cache = NULL; -typedef struct _test_data { +void setUp(void) +{ + /* Function run before every test */ + MEM_SIZE = lv_test_get_free_mem(); + cache = NULL; +} + +void tearDown(void) +{ + /* Function run after every test */ + if(cache) { + lv_cache_destroy(cache, NULL); + cache = NULL; + } + TEST_ASSERT_MEM_LEAK_LESS_THAN(MEM_SIZE, 64); +} + +typedef struct { lv_cache_slot_size_t slot; - + void * data; // malloced data int32_t key1; int32_t key2; + uint32_t magic; +} test_data_t; - void * data; // malloced data -} test_data; - -static lv_cache_compare_res_t compare_cb(const test_data * lhs, const test_data * rhs) +static lv_cache_compare_res_t compare_cb(const test_data_t * lhs, + const test_data_t * rhs) { if(lhs->key1 != rhs->key1) { return lhs->key1 > rhs->key1 ? 1 : -1; @@ -33,52 +49,42 @@ static lv_cache_compare_res_t compare_cb(const test_data * lhs, const test_data return 0; } -static void free_cb(test_data * node, void * user_data) +static void free_cb(test_data_t * node, void * user_data) { LV_UNUSED(user_data); lv_free(node->data); } -void setUp(void) +static lv_cache_t * create_cache(const lv_cache_class_t * cache_class, + size_t max_size) { - /* Function run before every test */ - MEM_SIZE = lv_test_get_free_mem(); - lv_cache_ops_t ops = { - .compare_cb = (lv_cache_compare_cb_t) compare_cb, + .compare_cb = (lv_cache_compare_cb_t)compare_cb, .create_cb = NULL, .free_cb = (lv_cache_free_cb_t)free_cb, }; - cache = lv_cache_create(&lv_cache_class_lru_rb_size, sizeof(test_data), CACHE_SIZE_BYTES, ops); + return lv_cache_create(cache_class, sizeof(test_data_t), max_size, ops); } -void tearDown(void) +void test_cache_lru_rb_1(void) { - /* Function run after every test */ - lv_cache_destroy(cache, NULL); - cache = NULL; - - TEST_ASSERT_MEM_LEAK_LESS_THAN(MEM_SIZE, 32); -} - -void test_cache_1(void) -{ - + cache = create_cache(&lv_cache_class_lru_rb_size, CACHE_SIZE_BYTES); + TEST_ASSERT_NOT_NULL(cache); void * record_data_ptr = NULL; // create many node unless cache is full uint32_t curr_mem_size = 8; uint32_t curr_total_mem_size = 0; while(curr_total_mem_size < CACHE_SIZE_BYTES) { - test_data search_key = { - .slot.size = curr_mem_size, + test_data_t search_key = { .slot.size = curr_mem_size, - .key1 = (int32_t)curr_mem_size, - .key2 = (int32_t)curr_mem_size + 1 - }; + .key1 = (int32_t)curr_mem_size, + .key2 = (int32_t)curr_mem_size + 1 + }; // acquire cache first - lv_cache_entry_t * entry = lv_cache_acquire(cache, &search_key, NULL); + lv_cache_entry_t * entry = + lv_cache_acquire(cache, &search_key, NULL); if(entry != NULL) { continue; } @@ -87,7 +93,7 @@ void test_cache_1(void) entry = lv_cache_add(cache, &search_key, NULL); TEST_ASSERT_NOT_NULL(entry); - test_data * data = lv_cache_entry_get_data(entry); + test_data_t * data = lv_cache_entry_get_data(entry); TEST_ASSERT_NOT_NULL(data); data->data = lv_malloc(data->slot.size); @@ -102,7 +108,8 @@ void test_cache_1(void) curr_total_mem_size += curr_mem_size; curr_mem_size *= 2; - TEST_PRINTF("cache free: %d, allocated: %d", lv_cache_get_free_size(cache, NULL), curr_mem_size); + TEST_PRINTF("cache free: %d, allocated: %d", + lv_cache_get_free_size(cache, NULL), curr_mem_size); } /* @@ -116,13 +123,11 @@ void test_cache_1(void) /* * Search entry {key1 = 32, key2 = 33} */ - test_data search_key32 = { - .key1 = 32, - .key2 = 33 - }; - lv_cache_entry_t * entry_key32 = lv_cache_acquire(cache, &search_key32, NULL); + test_data_t search_key32 = { .key1 = 32, .key2 = 33 }; + lv_cache_entry_t * entry_key32 = + lv_cache_acquire(cache, &search_key32, NULL); - test_data * cached_data_key32 = lv_cache_entry_get_data(entry_key32); + test_data_t * cached_data_key32 = lv_cache_entry_get_data(entry_key32); TEST_ASSERT_EQUAL(record_data_ptr, cached_data_key32->data); /* @@ -136,19 +141,158 @@ void test_cache_1(void) * lv_rb_note_t (4 ptr + 1 int32 may align to 8 bit on 64 bit machine) + lv_ll (2 ptr + node_size). * Also, the def heap has some other aligned attributes. It'll also affect the final result. */ - TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_curr_free, - sizeof(lv_rb_node_t) - + sizeof(void *) + (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *)) - + 32); // the last 32 is an error in memory allocating + TEST_ASSERT_MEM_LEAK_LESS_THAN( + mem_curr_free, + sizeof(lv_rb_node_t) + sizeof(void *) + + (sizeof(lv_ll_node_t *) + sizeof(lv_ll_node_t *)) + + 32); // the last 32 is an error in memory allocating mem_curr_free = lv_test_get_free_mem(); lv_cache_release(cache, entry_key32, NULL); - TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_curr_free, - lv_cache_entry_get_size(sizeof(test_data)) + sizeof(void *) - + 32 - + 32); + TEST_ASSERT_MEM_LEAK_LESS_THAN( + mem_curr_free, lv_cache_entry_get_size(sizeof(test_data_t)) + + sizeof(void *) + 32 + 32); // Now the freed cache size should be 8 + 32 = 40 TEST_ASSERT_EQUAL(40, lv_cache_get_free_size(cache, NULL)); } +void cache_add_acquire_test(void) +{ + TEST_ASSERT_NOT_NULL(cache); + test_data_t expected_data[10]; + + for(size_t i = 0; i < 10; ++i) { + expected_data[i].key1 = i; + expected_data[i].key2 = i + 1; + lv_cache_entry_t * entry = + lv_cache_add(cache, &expected_data[i], NULL); + TEST_ASSERT_NOT_NULL(entry); + lv_cache_release(cache, entry, NULL); + } + + for(size_t i = 0; i < 10; ++i) { + lv_cache_entry_t * entry = + lv_cache_acquire(cache, &expected_data[i], NULL); + TEST_ASSERT_NOT_NULL(entry); + test_data_t * actual_data = lv_cache_entry_get_data(entry); + TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1); + TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2); + lv_cache_release(cache, entry, NULL); + } +} + +void cache_eviction_test(void) +{ + TEST_ASSERT_NOT_NULL(cache); + test_data_t expected_data[10]; + + for(size_t i = 0; i < 10; ++i) { + expected_data[i].key1 = i; + expected_data[i].key2 = i + 1; + lv_cache_entry_t * entry = + lv_cache_add(cache, &expected_data[i], NULL); + TEST_ASSERT_NOT_NULL(entry); + lv_cache_release(cache, entry, NULL); + } + + for(size_t i = 0; i < 10; ++i) { + lv_cache_entry_t * entry = + lv_cache_acquire(cache, &expected_data[i], NULL); + TEST_ASSERT_NOT_NULL(entry); + test_data_t * actual_data = lv_cache_entry_get_data(entry); + TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1); + TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2); + lv_cache_release(cache, entry, NULL); + } + test_data_t new_expected_entry = { + .key1 = 1000, + .key2 = 2000, + }; + lv_cache_entry_t * new_entry = + lv_cache_add(cache, &new_expected_entry, NULL); + TEST_ASSERT_NOT_NULL(new_entry); + test_data_t * new_entry_data = lv_cache_entry_get_data(new_entry); + TEST_ASSERT_EQUAL(new_expected_entry.key1, new_entry_data->key1); + TEST_ASSERT_EQUAL(new_expected_entry.key2, new_entry_data->key2); + + /* Check that we removed a previous entry */ + size_t original_entries_found_cnt = 0; + for(size_t i = 0; i < 10; ++i) { + lv_cache_entry_t * entry = + lv_cache_acquire(cache, &expected_data[i], NULL); + if(entry != NULL) { + original_entries_found_cnt++; + lv_cache_release(cache, entry, NULL); + } + } + TEST_ASSERT_EQUAL(9, original_entries_found_cnt); +} + +void test_cache_lru_rb_count_add_acquire(void) +{ + cache = create_cache(&lv_cache_class_lru_rb_count, 10); + cache_add_acquire_test(); +} + +void test_cache_lru_ll_count_add_acquire(void) +{ + cache = create_cache(&lv_cache_class_lru_ll_count, 10); + cache_add_acquire_test(); +} + +void test_cache_sc_da_add_acquire(void) +{ + cache = create_cache(&lv_cache_class_sc_da, 10); + cache_add_acquire_test(); +} + +void test_cache_sc_da_eviction(void) +{ + cache = create_cache(&lv_cache_class_sc_da, 10); + cache_eviction_test(); +} + +void test_cache_lru_rb_count_eviction(void) +{ + cache = create_cache(&lv_cache_class_lru_rb_count, 10); + cache_eviction_test(); +} + +void test_cache_lru_ll_count_eviction(void) +{ + cache = create_cache(&lv_cache_class_lru_ll_count, 10); + cache_eviction_test(); +} + +void test_cache_sc_da_eviction_second_chance_spares_referenced_entries(void) +{ + cache = create_cache(&lv_cache_class_sc_da, 10); + TEST_ASSERT_NOT_NULL(cache); + test_data_t expected_data[10]; + + for(size_t i = 0; i < 10; ++i) { + expected_data[i].key1 = i; + expected_data[i].key2 = i + 1; + lv_cache_add(cache, &expected_data[i], NULL); + } + + for(size_t i = 0; i < 10; ++i) { + lv_cache_entry_t * entry = + lv_cache_acquire(cache, &expected_data[i], NULL); + TEST_ASSERT_NOT_NULL(entry); + test_data_t * actual_data = lv_cache_entry_get_data(entry); + TEST_ASSERT_EQUAL(expected_data[i].key1, actual_data->key1); + TEST_ASSERT_EQUAL(expected_data[i].key2, actual_data->key2); + /* Do not release the entry so that it stays referenced*/ + } + + test_data_t new_expected_entry = { + .key1 = 1000, + .key2 = 2000, + }; + lv_cache_entry_t * new_entry = + lv_cache_add(cache, &new_expected_entry, NULL); + TEST_ASSERT_NULL(new_entry); +} + #endif