diff --git a/Makefile b/Makefile index 2ccb824..a53ae4c 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,7 @@ src/shadow-gaussian-lut.h: scripts/gen-shadow-lut.py libtwin.a_files-$(CONFIG_LOGGING) += src/log.c libtwin.a_files-$(CONFIG_CURSOR) += src/cursor.c libtwin.a_files-y += src/memstats.c +libtwin.a_files-$(CONFIG_MEM_TLSF) += src/mem-tlsf.c # Rendering backends # Screen compositing operations (always needed for screen buffer management) diff --git a/configs/Kconfig b/configs/Kconfig index b936a24..de875bb 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -180,6 +180,31 @@ config MEMORY_STATS malloc/free call. Useful for profiling memory consumption on constrained devices. +config MEM_TLSF + bool "Use TLSF allocator for heap" + default n + help + Replace system malloc with the TLSF (Two-Level Segregated Fit) + allocator backed by a fixed-size memory pool. Provides O(1) + allocation and deallocation with bounded fragmentation, suitable + for real-time embedded targets without a system heap. + + When disabled, the system malloc/free is used directly. + +config MEM_TLSF_POOL_SIZE + int "TLSF pool size in bytes" + default 65536 + range 4096 8388608 + depends on MEM_TLSF + help + Size of the static memory pool backing the TLSF allocator. + Must be large enough for all runtime allocations including + pixmap buffers. A 640x480 ARGB32 screen pixmap alone needs + ~1.2 MB; the SDL demo with multiple windows peaks at ~8 MB. + Embedded targets with small displays need far less (e.g. + 320x240 RGB565 = ~150 KB framebuffer + widget overhead). + Requests exceeding pool capacity return NULL. + comment "Logging is disabled" depends on !LOGGING diff --git a/include/twin.h b/include/twin.h index bef2e8b..06fe70b 100644 --- a/include/twin.h +++ b/include/twin.h @@ -644,26 +644,32 @@ typedef enum _twin_shape { TwinShapeEllipse /**< Elliptical shape */ } twin_shape_t; +/** + * Optional widget attributes, lazily allocated on first use. + * Widgets that never register a callback or request focus pay zero + * RAM cost for these fields. Allocated on first callback/focus use. + */ +typedef struct _twin_widget_ext { + twin_widget_proc_t callback; /**< Application callback (optional) */ + void *callback_data; /**< Callback user data */ + bool want_focus; /**< Focus request flag */ +} twin_widget_ext_t; + struct _twin_widget { /* Widget hierarchy */ twin_window_t *window; /**< Parent window */ twin_widget_t *next; /**< Next sibling widget */ twin_box_t *parent; /**< Parent container */ - /* Event handling: - * - handler: Framework event handler (processes paint, configure, etc.) - * - callback: Application callback (optional, receives button clicks, etc.) - */ - twin_widget_proc_t handler; /**< Widget event handler (framework) */ - twin_widget_proc_t callback; /**< Application callback (optional) */ - void *callback_data; /**< Callback user data */ - twin_rect_t extents; /**< Current geometry */ - twin_widget_t *copy_geom; /**< Geometry source widget */ + /* Event handling */ + twin_widget_proc_t handler; /**< Widget event handler (framework) */ + twin_widget_ext_t *ext; /**< Optional attributes (lazy, may be NULL) */ + twin_rect_t extents; /**< Current geometry */ + twin_widget_t *copy_geom; /**< Geometry source widget (optional) */ /* Widget state */ - bool paint; /**< Needs painting */ - bool layout; /**< Needs layout */ - bool want_focus; /**< Focus request flag */ + bool paint; /**< Needs painting */ + bool layout; /**< Needs layout */ /* Widget appearance */ twin_argb32_t background; /**< Background color */ @@ -753,6 +759,15 @@ void twin_widget_set_callback(twin_widget_t *widget, twin_widget_proc_t callback, void *data); +/** + * Set whether a widget should take focus on pointer interaction. + * @widget : Widget to configure + * @focusable : true to route key/UCS4 events to this widget after click + * + * This is primarily used by custom widgets that handle keyboard input. + */ +void twin_widget_set_focusable(twin_widget_t *widget, bool focusable); + /** * Create default mouse cursor pixmap * @hx : Output hotspot X coordinate diff --git a/src/api.c b/src/api.c index 4d79955..b2ef82b 100644 --- a/src/api.c +++ b/src/api.c @@ -22,6 +22,9 @@ extern twin_backend_t g_twin_backend; */ twin_context_t *twin_create(int width, int height) { + /* Initialize the memory pool before any allocations occur. */ + twin_mem_pool_init(); + /* Runtime check for missing backend */ if (!g_twin_backend.init) { log_error("Backend not registered - no init function"); diff --git a/src/box.c b/src/box.c index 1eb983c..d13c233 100644 --- a/src/box.c +++ b/src/box.c @@ -41,7 +41,7 @@ static twin_dispatch_result_t _twin_box_query_geometry(twin_box_t *box) for (twin_widget_t *child = box->children; child; child = child->next) { if (child->layout) { ev.kind = TwinEventQueryGeometry; - child->handler(child, &ev, child->callback_data); + child->handler(child, &ev, _twin_widget_callback_data(child)); } if (box->dir == TwinBoxHorz) { preferred.width += child->preferred.width; @@ -129,7 +129,7 @@ static twin_dispatch_result_t _twin_box_configure(twin_box_t *box) extents.bottom != child->extents.bottom) { ev.kind = TwinEventConfigure; ev.u.configure.extents = extents; - child->handler(child, &ev, child->callback_data); + child->handler(child, &ev, _twin_widget_callback_data(child)); } } return TwinDispatchContinue; @@ -167,7 +167,7 @@ twin_dispatch_result_t _twin_box_dispatch(twin_widget_t *widget, /* Send destroy event to child */ ev.kind = TwinEventDestroy; - child->handler(child, &ev, child->callback_data); + child->handler(child, &ev, _twin_widget_callback_data(child)); } break; case TwinEventQueryGeometry: @@ -178,7 +178,7 @@ twin_dispatch_result_t _twin_box_dispatch(twin_widget_t *widget, twin_window_show(widget->window); box->button_down = _twin_box_xy_to_widget(box, event->u.pointer.x, event->u.pointer.y); - if (box->button_down && box->button_down->want_focus) + if (box->button_down && _twin_widget_want_focus(box->button_down)) box->focus = box->button_down; fallthrough; case TwinEventButtonUp: @@ -188,7 +188,8 @@ twin_dispatch_result_t _twin_box_dispatch(twin_widget_t *widget, ev = *event; ev.u.pointer.x -= child->extents.left; ev.u.pointer.y -= child->extents.top; - return child->handler(child, &ev, child->callback_data); + return child->handler(child, &ev, + _twin_widget_callback_data(child)); } break; case TwinEventKeyDown: @@ -196,7 +197,7 @@ twin_dispatch_result_t _twin_box_dispatch(twin_widget_t *widget, case TwinEventUcs4: if (box->focus) return box->focus->handler(box->focus, event, - box->focus->callback_data); + _twin_widget_callback_data(box->focus)); break; case TwinEventPaint: box->widget.paint = false; @@ -215,7 +216,7 @@ twin_dispatch_result_t _twin_box_dispatch(twin_widget_t *widget, twin_pixmap_set_clip(pixmap, child->extents); twin_pixmap_origin_to_clip(pixmap); child->paint = false; - child->handler(child, event, child->callback_data); + child->handler(child, event, _twin_widget_callback_data(child)); twin_pixmap_restore_clip(pixmap, clip); twin_pixmap_set_origin(pixmap, ox, oy); } diff --git a/src/button.c b/src/button.c index 142c490..d3f2996 100644 --- a/src/button.c +++ b/src/button.c @@ -46,11 +46,12 @@ twin_dispatch_result_t _twin_button_dispatch(twin_widget_t *widget, _twin_button_set_label_offset(button); /* Invoke widget callback for button press */ - if (widget->callback) { + if (_twin_widget_callback(widget)) { twin_event_t press_event = *event; press_event.kind = TwinEventButtonSignalDown; press_event.u.button_signal.signal = TwinButtonSignalDown; - (*widget->callback)(widget, &press_event, widget->callback_data); + _twin_widget_callback(widget)(widget, &press_event, + _twin_widget_callback_data(widget)); } return TwinDispatchDone; break; @@ -72,12 +73,12 @@ twin_dispatch_result_t _twin_button_dispatch(twin_widget_t *widget, _twin_button_set_label_offset(button); /* Invoke widget callback for button release (click) */ - if (widget->callback) { + if (_twin_widget_callback(widget)) { twin_event_t release_event = *event; release_event.kind = TwinEventButtonSignalUp; release_event.u.button_signal.signal = TwinButtonSignalUp; - (*widget->callback)(widget, &release_event, - widget->callback_data); + _twin_widget_callback(widget)( + widget, &release_event, _twin_widget_callback_data(widget)); } } return TwinDispatchDone; diff --git a/src/mem-tlsf.c b/src/mem-tlsf.c new file mode 100644 index 0000000..45a9df7 --- /dev/null +++ b/src/mem-tlsf.c @@ -0,0 +1,526 @@ +/* + * Twin - A Tiny Window System + * Copyright (c) 2026 National Cheng Kung University, Taiwan + * All rights reserved. + * + * Minimal TLSF (Two-Level Segregated Fit) allocator for embedded targets. + * Based on tlsf-bsd (BSD-3-Clause), stripped to static-pool essentials: + * pool_init, malloc, realloc, free. No dynamic growth, no aligned alloc, + * no debug/check/stats walkers, no ASan/poison hooks. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "twin_private.h" + +#if defined(CONFIG_MEM_TLSF) + +/* -- TLSF configuration ------------------------------------------------- */ + +#define ALIGN_SIZE ((size_t) 1 << ALIGN_SHIFT) +#if __SIZE_WIDTH__ == 64 +#define ALIGN_SHIFT 3 +#else +#define ALIGN_SHIFT 2 +#endif + +#define SL_SHIFT 5 +#define SL_COUNT (1U << SL_SHIFT) + +/* Cap FL to keep control structure small for embedded pools. + * FL_MAX = 24 supports pools up to 8 MB, which exceeds any sane + * CONFIG_MEM_TLSF_POOL_SIZE for an embedded UI toolkit. + */ +#define FL_MAX 24 + +#define FL_SHIFT (SL_SHIFT + ALIGN_SHIFT) +#define FL_COUNT (FL_MAX - FL_SHIFT + 1) + +#define BLOCK_BIT_FREE ((size_t) 1) +#define BLOCK_BIT_PREV_FREE ((size_t) 2) +#define BLOCK_BITS (BLOCK_BIT_FREE | BLOCK_BIT_PREV_FREE) + +#define BLOCK_OVERHEAD (sizeof(size_t)) +#define BLOCK_SIZE_MIN (sizeof(tlsf_block_t) - sizeof(tlsf_block_t *)) +#define BLOCK_SIZE_MAX ((size_t) 1 << (FL_MAX - 1)) +#define BLOCK_SIZE_SMALL ((size_t) 1 << FL_SHIFT) + +#define TLSF_MAX_SIZE (((size_t) 1 << (FL_MAX - 1)) - sizeof(size_t)) + +#define UNLIKELY(x) __builtin_expect(!!(x), false) + +/* Force inlining for TLSF internals -- these are hot-path helpers that + * must inline even at -O0 to keep allocation cost bounded. + */ +#define TLSF_INLINE static inline __attribute__((always_inline)) + +/* -- TLSF data structures ------------------------------------------------ */ + +typedef struct tlsf_block { + struct tlsf_block *prev; + size_t header; + struct tlsf_block *next_free, *prev_free; +} tlsf_block_t; + +typedef struct { + uint32_t fl, sl[FL_COUNT]; + void *arena; + size_t size; + struct tlsf_block *block[FL_COUNT][SL_COUNT]; + struct tlsf_block block_null; +} tlsf_t; + +/* -- Helpers -------------------------------------------------------------- */ + +/* Use portable CLZ wrappers from twin_private.h (twin_clz / twin_clzll) + * instead of raw __builtin_clz* so this compiles on MSVC and generic + * targets without GCC/Clang intrinsics. + */ +/* Precondition: x != 0. CLZ of zero is undefined on all platforms. */ +TLSF_INLINE uint32_t bitmap_ffs(uint32_t x) +{ + /* ctz(x) = 31 - clz(x & -x) for nonzero x */ + return (uint32_t) (31 - twin_clz(x & (~x + 1))); +} + +/* Precondition: x != 0. */ +TLSF_INLINE uint32_t log2floor(size_t x) +{ +#if __SIZE_WIDTH__ == 64 + return (uint32_t) (63 - twin_clzll(x)); +#else + return (uint32_t) (31 - twin_clz((uint32_t) x)); +#endif +} + +TLSF_INLINE size_t block_size(const tlsf_block_t *block) +{ + return block->header & ~BLOCK_BITS; +} + +TLSF_INLINE void block_set_size(tlsf_block_t *block, size_t size) +{ + block->header = size | (block->header & BLOCK_BITS); +} + +TLSF_INLINE bool block_is_free(const tlsf_block_t *block) +{ + return !!(block->header & BLOCK_BIT_FREE); +} + +TLSF_INLINE bool block_is_prev_free(const tlsf_block_t *block) +{ + return !!(block->header & BLOCK_BIT_PREV_FREE); +} + +TLSF_INLINE void block_set_prev_free(tlsf_block_t *block, bool free) +{ + block->header = free ? block->header | BLOCK_BIT_PREV_FREE + : block->header & ~BLOCK_BIT_PREV_FREE; +} + +TLSF_INLINE size_t align_up(size_t x, size_t align) +{ + return (((x - 1) | (align - 1)) + 1); +} + +TLSF_INLINE char *align_ptr(char *p, size_t align) +{ + uintptr_t addr = (uintptr_t) p; + uintptr_t aligned = align_up(addr, align); + return p + (aligned - addr); +} + +TLSF_INLINE char *block_payload(tlsf_block_t *block) +{ + return (char *) block + offsetof(tlsf_block_t, header) + BLOCK_OVERHEAD; +} + +TLSF_INLINE tlsf_block_t *to_block(void *ptr) +{ + return (tlsf_block_t *) ptr; +} + +TLSF_INLINE tlsf_block_t *block_from_payload(void *ptr) +{ + return to_block((char *) ptr - offsetof(tlsf_block_t, header) - + BLOCK_OVERHEAD); +} + +TLSF_INLINE tlsf_block_t *block_next(tlsf_block_t *block) +{ + return to_block(block_payload(block) + block_size(block) - BLOCK_OVERHEAD); +} + +TLSF_INLINE tlsf_block_t *block_link_next(tlsf_block_t *block) +{ + tlsf_block_t *next = block_next(block); + next->prev = block; + return next; +} + +TLSF_INLINE void block_set_free(tlsf_block_t *block, bool free) +{ + block->header = + free ? block->header | BLOCK_BIT_FREE : block->header & ~BLOCK_BIT_FREE; + block_set_prev_free(block_link_next(block), free); +} + +TLSF_INLINE size_t adjust_size(size_t size, size_t align) +{ + if (UNLIKELY(size > TLSF_MAX_SIZE)) + return size; + size = align_up(size, align); + return size < BLOCK_SIZE_MIN ? BLOCK_SIZE_MIN : size; +} + +TLSF_INLINE size_t round_block_size(size_t size) +{ + uint32_t lg = log2floor(size); + size_t is_large = (size_t) (lg >= (uint32_t) FL_SHIFT); + uint32_t shift = + (lg - (uint32_t) SL_SHIFT) & ((uint32_t) (__SIZE_WIDTH__ - 1)); + size_t round = is_large << shift; + size_t t = round - is_large; + return (size + t) & ~t; +} + +TLSF_INLINE void mapping(size_t size, uint32_t *fl, uint32_t *sl) +{ + uint32_t t = log2floor(size); + uint32_t small = -(uint32_t) (t < (uint32_t) FL_SHIFT); + *fl = ~small & (t - (uint32_t) FL_SHIFT + 1); + uint32_t shift = + (t - (uint32_t) SL_SHIFT) & ((uint32_t) (__SIZE_WIDTH__ - 1)); + uint32_t sl_large = (uint32_t) (size >> shift) ^ SL_COUNT; + uint32_t sl_small = (uint32_t) (size >> ALIGN_SHIFT); + *sl = (~small & sl_large) | (small & sl_small); +} + +TLSF_INLINE tlsf_block_t *block_find_suitable(tlsf_t *t, + uint32_t *fl, + uint32_t *sl) +{ + uint32_t sl_map = t->sl[*fl] & (~0U << *sl); + if (!sl_map) { + uint32_t fl_map = t->fl & ((*fl + 1 >= 32) ? 0U : (~0U << (*fl + 1))); + if (UNLIKELY(!fl_map)) + return NULL; + *fl = bitmap_ffs(fl_map); + sl_map = t->sl[*fl]; + } + *sl = bitmap_ffs(sl_map); + return t->block[*fl][*sl]; +} + +TLSF_INLINE void remove_free_block(tlsf_t *t, + tlsf_block_t *block, + uint32_t fl, + uint32_t sl) +{ + tlsf_block_t *prev = block->prev_free; + tlsf_block_t *next = block->next_free; + next->prev_free = prev; + prev->next_free = next; + if (t->block[fl][sl] == block) { + t->block[fl][sl] = next; + if (next == &t->block_null) { + t->sl[fl] &= ~(1U << sl); + if (!t->sl[fl]) + t->fl &= ~(1U << fl); + } + } +} + +TLSF_INLINE void insert_free_block(tlsf_t *t, + tlsf_block_t *block, + uint32_t fl, + uint32_t sl) +{ + tlsf_block_t *current = t->block[fl][sl]; + block->next_free = current; + block->prev_free = &t->block_null; + current->prev_free = block; + t->block[fl][sl] = block; + t->fl |= 1U << fl; + t->sl[fl] |= 1U << sl; +} + +TLSF_INLINE void block_remove(tlsf_t *t, tlsf_block_t *block) +{ + uint32_t fl, sl; + mapping(block_size(block), &fl, &sl); + remove_free_block(t, block, fl, sl); +} + +TLSF_INLINE void block_insert(tlsf_t *t, tlsf_block_t *block) +{ + uint32_t fl, sl; + mapping(block_size(block), &fl, &sl); + insert_free_block(t, block, fl, sl); +} + +TLSF_INLINE bool block_can_trim(tlsf_block_t *block, size_t size) +{ + return block_size(block) >= BLOCK_OVERHEAD + BLOCK_SIZE_MIN + size; +} + +TLSF_INLINE tlsf_block_t *block_split(tlsf_block_t *block, size_t size) +{ + tlsf_block_t *rest = to_block(block_payload(block) + size - BLOCK_OVERHEAD); + size_t rest_size = block_size(block) - (size + BLOCK_OVERHEAD); + rest->header = rest_size; + block_set_free(rest, true); + block_set_size(block, size); + return rest; +} + +TLSF_INLINE tlsf_block_t *block_absorb(tlsf_block_t *prev, tlsf_block_t *block) +{ + prev->header += block_size(block) + BLOCK_OVERHEAD; + block_link_next(prev); + return prev; +} + +TLSF_INLINE tlsf_block_t *block_merge_prev(tlsf_t *t, tlsf_block_t *block) +{ + if (block_is_prev_free(block)) { + tlsf_block_t *prev = block->prev; + block_remove(t, prev); + block = block_absorb(prev, block); + } + return block; +} + +TLSF_INLINE tlsf_block_t *block_merge_next(tlsf_t *t, tlsf_block_t *block) +{ + tlsf_block_t *next = block_next(block); + if (block_is_free(next)) { + block_remove(t, next); + block = block_absorb(block, next); + } + return block; +} + +TLSF_INLINE void block_rtrim_free(tlsf_t *t, tlsf_block_t *block, size_t size) +{ + if (!block_can_trim(block, size)) + return; + tlsf_block_t *rest = block_split(block, size); + block_link_next(block); + block_set_prev_free(rest, true); + block_insert(t, rest); +} + +TLSF_INLINE void block_rtrim_used(tlsf_t *t, tlsf_block_t *block, size_t size) +{ + if (!block_can_trim(block, size)) + return; + tlsf_block_t *rest = block_split(block, size); + block_set_prev_free(rest, false); + rest = block_merge_next(t, rest); + block_insert(t, rest); +} + +TLSF_INLINE void *block_use(tlsf_t *t, tlsf_block_t *block, size_t size) +{ + block_rtrim_free(t, block, size); + block_set_free(block, false); + return block_payload(block); +} + +TLSF_INLINE tlsf_block_t *block_find_free(tlsf_t *t, size_t *size) +{ + size_t req = *size; + *size = round_block_size(*size); + uint32_t fl, sl; + mapping(*size, &fl, &sl); + tlsf_block_t *block = block_find_suitable(t, &fl, &sl); + if (UNLIKELY(!block)) + return NULL; + /* Use the original rounded request size, not the bin minimum. + * The upstream TLSF sets *size = mapping_size(fl, sl) to keep + * freed blocks in consistent bins, but when a small request + * hits a large bin the wasted space is catastrophic (a 48-byte + * request from an 8 MB block would consume the entire bin-minimum + * of ~96 KB). Trimming to the request size instead produces + * tighter splits and avoids pool exhaustion. + */ + *size = req; + remove_free_block(t, block, fl, sl); + return block; +} + +/* -- TLSF API (static pool only) ----------------------------------------- */ + +static size_t tlsf_pool_init(tlsf_t *t, void *mem, size_t bytes) +{ + if (!t || !mem) + return 0; + + memset(t, 0, sizeof(*t)); + for (uint32_t i = 0; i < FL_COUNT; i++) + for (uint32_t j = 0; j < SL_COUNT; j++) + t->block[i][j] = &t->block_null; + + char *start = align_ptr((char *) mem, ALIGN_SIZE); + size_t adj = (size_t) (start - (char *) mem); + if (bytes <= adj) + return 0; + + size_t pool_bytes = (bytes - adj) & ~(ALIGN_SIZE - 1); + if (pool_bytes < 2 * BLOCK_OVERHEAD + BLOCK_SIZE_MIN) + return 0; + + size_t free_size = pool_bytes - 2 * BLOCK_OVERHEAD; + free_size &= ~(ALIGN_SIZE - 1); + if (free_size < BLOCK_SIZE_MIN || free_size > BLOCK_SIZE_MAX) + return 0; + + t->arena = start; + + tlsf_block_t *block = to_block(start - BLOCK_OVERHEAD); + block->header = free_size | BLOCK_BIT_FREE; + block_insert(t, block); + + tlsf_block_t *sentinel = block_link_next(block); + sentinel->header = BLOCK_BIT_PREV_FREE; + + t->size = free_size + 2 * BLOCK_OVERHEAD; + return free_size; +} + +static void *tlsf_malloc(tlsf_t *t, size_t size) +{ + size = adjust_size(size, ALIGN_SIZE); + if (UNLIKELY(size > TLSF_MAX_SIZE)) + return NULL; + + /* Fast path: small sizes (FL=0) use linear SL mapping directly. */ + if (size < BLOCK_SIZE_SMALL) { + uint32_t sl = (uint32_t) (size >> ALIGN_SHIFT); + uint32_t sl_map = t->sl[0] & (~0U << sl); + if (sl_map) { + uint32_t found_sl = bitmap_ffs(sl_map); + size = (size_t) found_sl << ALIGN_SHIFT; + tlsf_block_t *block = t->block[0][found_sl]; + remove_free_block(t, block, 0, found_sl); + return block_use(t, block, size); + } + } + + tlsf_block_t *block = block_find_free(t, &size); + if (UNLIKELY(!block)) + return NULL; + return block_use(t, block, size); +} + +static void tlsf_free(tlsf_t *t, void *mem) +{ + if (UNLIKELY(!mem)) + return; + tlsf_block_t *block = block_from_payload(mem); + block_set_free(block, true); + block = block_merge_prev(t, block); + block = block_merge_next(t, block); + block_insert(t, block); +} + +static void *tlsf_realloc(tlsf_t *t, void *mem, size_t size) +{ + if (UNLIKELY(mem && !size)) { + tlsf_free(t, mem); + return NULL; + } + if (UNLIKELY(!mem)) + return tlsf_malloc(t, size); + + tlsf_block_t *block = block_from_payload(mem); + size_t avail = block_size(block); + size = adjust_size(size, ALIGN_SIZE); + if (UNLIKELY(size > TLSF_MAX_SIZE)) + return NULL; + + if (size > avail) { + /* Try in-place forward expansion */ + tlsf_block_t *next = block_next(block); + if (block_is_free(next) && + size <= avail + block_size(next) + BLOCK_OVERHEAD) { + block_merge_next(t, block); + block_set_prev_free(block_next(block), false); + } else { + void *dst = tlsf_malloc(t, size); + if (dst) { + memcpy(dst, mem, avail); + tlsf_free(t, mem); + } + return dst; + } + } + + block_rtrim_used(t, block, size); + return mem; +} + +/* -- Mado raw allocator backend ------------------------------------------ */ + +static uint8_t pool_storage[CONFIG_MEM_TLSF_POOL_SIZE] + __attribute__((aligned(8))); +static tlsf_t tlsf_instance; +static bool pool_ready; + +static bool twin_mem_pool_ensure_ready(void) +{ + if (!pool_ready) + twin_mem_pool_init(); + return pool_ready; +} + +void twin_mem_pool_init(void) +{ + if (pool_ready) + return; + size_t usable = + tlsf_pool_init(&tlsf_instance, pool_storage, sizeof(pool_storage)); + pool_ready = (usable > 0); +} + +void *twin_raw_malloc(size_t size) +{ + if (!twin_mem_pool_ensure_ready()) + return NULL; + void *ptr = tlsf_malloc(&tlsf_instance, size); + if (!ptr) + log_error( + "TLSF pool exhausted (requested %zu bytes, pool %d bytes). " + "Increase CONFIG_MEM_TLSF_POOL_SIZE.", + size, CONFIG_MEM_TLSF_POOL_SIZE); + return ptr; +} + +void *twin_raw_calloc(size_t n, size_t size) +{ + if (n && size > SIZE_MAX / n) + return NULL; + if (!twin_mem_pool_ensure_ready()) + return NULL; + size_t total = n * size; + void *ptr = tlsf_malloc(&tlsf_instance, total); + if (ptr) + memset(ptr, 0, total); + return ptr; +} + +void *twin_raw_realloc(void *ptr, size_t size) +{ + if (!ptr && !twin_mem_pool_ensure_ready()) + return NULL; + return tlsf_realloc(&tlsf_instance, ptr, size); +} + +void twin_raw_free(void *ptr) +{ + tlsf_free(&tlsf_instance, ptr); +} + +#endif /* CONFIG_MEM_TLSF */ diff --git a/src/memstats.c b/src/memstats.c index 06132f4..726a32a 100644 --- a/src/memstats.c +++ b/src/memstats.c @@ -15,6 +15,12 @@ /* Small inline tracker storage keeps the common case allocation-free, * but the table can grow with libc realloc so usage accounting remains * correct when many allocations are live at once. + * + * The tracking table itself always uses libc malloc/realloc/free, never + * the raw allocator backend (twin_raw_*). This avoids a circular + * dependency when CONFIG_MEM_TLSF is enabled: the TLSF pool allocations + * are tracked in the table, so the table storage must not come from the + * same pool. */ #define MEMTBL_INLINE_CAPACITY 512 @@ -106,10 +112,10 @@ void *_twin_malloc(size_t size, const char *file, int line) { (void) file; (void) line; - void *ptr = malloc(size); + void *ptr = twin_raw_malloc(size); if (ptr) { if (!memtbl_insert(ptr, size)) { - free(ptr); + twin_raw_free(ptr); return NULL; } stats.current_bytes += size; @@ -128,11 +134,11 @@ void *_twin_calloc(size_t n, size_t size, const char *file, int line) /* Overflow check */ if (n && size > SIZE_MAX / n) return NULL; - void *ptr = calloc(n, size); + void *ptr = twin_raw_calloc(n, size); if (ptr) { size_t total = n * size; if (!memtbl_insert(ptr, total)) { - free(ptr); + twin_raw_free(ptr); return NULL; } stats.current_bytes += total; @@ -154,7 +160,7 @@ void *_twin_realloc(void *old, size_t size, const char *file, int line) stats.current_bytes -= old_size; if (old) stats.total_frees++; - free(old); + twin_raw_free(old); return NULL; } @@ -162,7 +168,7 @@ void *_twin_realloc(void *old, size_t size, const char *file, int line) if (old_idx < 0 && memtbl_count == memtbl_capacity && !memtbl_grow()) return NULL; - void *ptr = realloc(old, size); + void *ptr = twin_raw_realloc(old, size); if (!ptr) return NULL; @@ -197,7 +203,7 @@ void _twin_free(void *ptr, const char *file, int line) size_t sz = memtbl_remove(ptr); stats.current_bytes -= sz; stats.total_frees++; - free(ptr); + twin_raw_free(ptr); } void twin_memory_get_info(twin_memory_info_t *info) diff --git a/src/twin_private.h b/src/twin_private.h index 5578b52..b3cc6b7 100644 --- a/src/twin_private.h +++ b/src/twin_private.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "twin.h" @@ -762,7 +763,9 @@ static inline int twin_clzll(uint64_t v) #else /* generic implementation */ static inline int twin_clz(uint32_t v) { - /* http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn */ + /* De Bruijn log2floor + inversion to get count of leading zeros. + * http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn + */ static const uint8_t mul_debruijn[] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31, @@ -774,27 +777,15 @@ static inline int twin_clz(uint32_t v) v |= v >> 8; v |= v >> 16; - return mul_debruijn[(uint32_t) (v * 0x07C4ACDDU) >> 27]; + return 31 - (int) mul_debruijn[(uint32_t) (v * 0x07C4ACDDU) >> 27]; } static inline int twin_clzll(uint64_t v) { - /* https://stackoverflow.com/questions/21888140/de-bruijn-algorithm-binary-digit-count-64bits-c-sharp + /* Split into high/low 32-bit halves and reuse the verified 32-bit + * de Bruijn CLZ. Avoids a separate 64-bit lookup table. */ - static const uint8_t mul_debruijn[] = { - 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, - 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, - 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, - 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, - }; - - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - v |= v >> 32; - - return mul_debruijn[(uint64_t) (v * 0x022fdd63cc95386dUL) >> 58]; + uint32_t hi = (uint32_t) (v >> 32); + return hi ? twin_clz(hi) : 32 + twin_clz((uint32_t) v); } #endif @@ -999,6 +990,37 @@ typedef struct { size_t total_bytes; } twin_memstats_t; +/* + * Raw allocator backend. When CONFIG_MEM_TLSF is enabled these route + * through a fixed-size TLSF pool; otherwise they are trivial wrappers + * around libc malloc/calloc/realloc/free. + */ +#if defined(CONFIG_MEM_TLSF) +void twin_mem_pool_init(void); +void *twin_raw_malloc(size_t size); +void *twin_raw_calloc(size_t n, size_t size); +void *twin_raw_realloc(void *ptr, size_t size); +void twin_raw_free(void *ptr); +#else +static inline void twin_mem_pool_init(void) {} +static inline void *twin_raw_malloc(size_t sz) +{ + return malloc(sz); +} +static inline void *twin_raw_calloc(size_t n, size_t sz) +{ + return calloc(n, sz); +} +static inline void *twin_raw_realloc(void *p, size_t sz) +{ + return realloc(p, sz); +} +static inline void twin_raw_free(void *p) +{ + free(p); +} +#endif + #if defined(CONFIG_MEMORY_STATS) void *_twin_malloc(size_t size, const char *file, int line); void *_twin_calloc(size_t n, size_t size, const char *file, int line); @@ -1009,10 +1031,43 @@ void _twin_free(void *ptr, const char *file, int line); #define twin_realloc(p, sz) _twin_realloc(p, sz, __FILE__, __LINE__) #define twin_free(p) _twin_free(p, __FILE__, __LINE__) #else -#define twin_malloc(sz) malloc(sz) -#define twin_calloc(n, sz) calloc(n, sz) -#define twin_realloc(p, sz) realloc(p, sz) -#define twin_free(p) free(p) +#define twin_malloc(sz) twin_raw_malloc(sz) +#define twin_calloc(n, sz) twin_raw_calloc(n, sz) +#define twin_realloc(p, sz) twin_raw_realloc(p, sz) +#define twin_free(p) twin_raw_free(p) #endif +/* + * Lazy widget extension accessors. + * + * The optional fields (callback, callback_data, want_focus) live in a + * separately allocated twin_widget_ext_t block. Widgets that never + * register a callback or request focus keep ext == NULL and pay only + * one pointer. + */ + +/* Allocate the extension block if absent. Returns ext or NULL on OOM. */ +static inline twin_widget_ext_t *_twin_widget_ensure_ext(twin_widget_t *w) +{ + if (w->ext) + return w->ext; + w->ext = twin_calloc(1, sizeof(twin_widget_ext_t)); + return w->ext; +} + +static inline twin_widget_proc_t _twin_widget_callback(const twin_widget_t *w) +{ + return w->ext ? w->ext->callback : NULL; +} + +static inline void *_twin_widget_callback_data(const twin_widget_t *w) +{ + return w->ext ? w->ext->callback_data : NULL; +} + +static inline bool _twin_widget_want_focus(const twin_widget_t *w) +{ + return w->ext ? w->ext->want_focus : false; +} + #endif /* _TWIN_PRIVATE_H_ */ diff --git a/src/widget.c b/src/widget.c index 168804c..e192284 100644 --- a/src/widget.c +++ b/src/widget.c @@ -88,7 +88,7 @@ twin_dispatch_result_t _twin_widget_dispatch(twin_widget_t *widget, if (widget->copy_geom) { twin_widget_t *copy = widget->copy_geom; if (copy->layout) - copy->handler(copy, event, copy->callback_data); + copy->handler(copy, event, _twin_widget_callback_data(copy)); widget->preferred = copy->preferred; return TwinDispatchDone; } @@ -101,7 +101,8 @@ twin_dispatch_result_t _twin_widget_dispatch(twin_widget_t *widget, widget->paint = false; break; case TwinEventDestroy: - /* Base widget has no special cleanup */ + twin_free(widget->ext); + widget->ext = NULL; break; default: break; @@ -128,17 +129,15 @@ void _twin_widget_init(twin_widget_t *widget, widget->window = window; widget->parent = parent; + widget->ext = NULL; widget->copy_geom = NULL; widget->paint = true; widget->layout = true; - widget->want_focus = false; widget->background = 0x00000000; widget->extents.left = widget->extents.top = 0; widget->extents.right = widget->extents.bottom = 0; widget->preferred = preferred; widget->handler = handler; - widget->callback = NULL; - widget->callback_data = NULL; widget->shape = TwinShapeRectangle; widget->radius = twin_int_to_fixed(12); } @@ -486,6 +485,28 @@ void twin_widget_set_callback(twin_widget_t *widget, { if (!widget) return; - widget->callback = callback; - widget->callback_data = data; + twin_widget_ext_t *ext = _twin_widget_ensure_ext(widget); + if (!ext) + return; + ext->callback = callback; + ext->callback_data = data; +} + +void twin_widget_set_focusable(twin_widget_t *widget, bool focusable) +{ + if (!widget) + return; + + if (!focusable) { + if (widget->ext) + widget->ext->want_focus = false; + if (widget->parent && widget->parent->focus == widget) + widget->parent->focus = NULL; + return; + } + + twin_widget_ext_t *ext = _twin_widget_ensure_ext(widget); + if (!ext) + return; + ext->want_focus = true; }