diff --git a/repos/gems/include/gems/cached_font.h b/repos/gems/include/gems/cached_font.h
index ebd6b2a3d..89a89747f 100644
--- a/repos/gems/include/gems/cached_font.h
+++ b/repos/gems/include/gems/cached_font.h
@@ -11,11 +11,10 @@
* under the terms of the GNU Affero General Public License version 3.
*/
-#ifndef _INCLUDE__GEMS__CACHED_FONT_T_
-#define _INCLUDE__GEMS__CACHED_FONT_T_
+#ifndef _INCLUDE__GEMS__CACHED_FONT_H_
+#define _INCLUDE__GEMS__CACHED_FONT_H_
-#include
-#include
+#include
#include
namespace Genode { class Cached_font; }
@@ -23,178 +22,103 @@ namespace Genode { class Cached_font; }
class Genode::Cached_font : public Text_painter::Font
{
- public:
-
- struct Stats
- {
- unsigned misses;
- unsigned hits;
- unsigned consumed_bytes;
-
- void print(Output &out) const
- {
- Genode::print(out, "used: ", consumed_bytes/1024, " KiB, "
- "hits: ", hits, ", misses: ", misses);
- }
- };
-
private:
typedef Text_painter::Area Area;
typedef Text_painter::Font Font;
typedef Text_painter::Glyph Glyph;
- struct Time { unsigned value; };
-
- Allocator &_alloc;
- Font const &_font;
- size_t const _limit;
- Time mutable _now { 0 };
- Stats mutable _stats { };
-
- class Cached_glyph : Avl_node
+ struct Cached_glyph : Glyph, Noncopyable
{
- private:
+ Glyph::Opacity _values[];
- friend class Avl_node;
- friend class Avl_tree;
- friend class Cached_font;
+ /*
+ * The number of values is not statically known but runtime-
+ * dependent. The values are stored directly after the
+ * 'Cached_glyph' object.
+ */
- Codepoint const _codepoint;
- Glyph const _glyph;
- Time _last_used;
-
- Glyph::Opacity _values[];
-
- bool _higher(Codepoint const other) const
- {
- return _codepoint.value > other.value;
- }
-
- unsigned _importance(Time now) const
- {
- return now.value - _last_used.value;
- }
-
- public:
-
- Cached_glyph(Codepoint c, Glyph const &glyph, Time now)
- :
- _codepoint(c),
- _glyph({ .width = glyph.width,
- .height = glyph.height,
- .vpos = glyph.vpos,
- .advance = glyph.advance,
- .values = _values }),
- _last_used(now)
- {
- for (unsigned i = 0; i < glyph.num_values(); i++)
- _values[i] = glyph.values[i];
- }
-
- /**
- * Avl_node interface
- */
- bool higher(Cached_glyph *c) { return _higher(c->_codepoint); }
-
- Cached_glyph *find_by_codepoint(Codepoint requested)
- {
- if (_codepoint.value == requested.value) return this;
-
- Cached_glyph *c = Avl_node::child(_higher(requested));
-
- return c ? c->find_by_codepoint(requested) : nullptr;
- }
-
- Cached_glyph *find_least_recently_used(Time now)
- {
- Cached_glyph *result = this;
-
- for (unsigned i = 0; i < 2; i++) {
- Cached_glyph *c = Avl_node::child(i);
- if (c && c->_importance(now) > result->_importance(now))
- result = c;
- }
- return result;
- }
-
- void mark_as_used(Time now) { _last_used = now; }
-
- void apply(Font::Apply_fn const &fn) const { fn.apply(_glyph); }
+ Cached_glyph(Glyph const &glyph)
+ :
+ Glyph({ .width = glyph.width,
+ .height = glyph.height,
+ .vpos = glyph.vpos,
+ .advance = glyph.advance,
+ .values = _values })
+ {
+ for (unsigned i = 0; i < glyph.num_values(); i++)
+ _values[i] = glyph.values[i];
+ }
};
- Avl_tree mutable _avl_tree { };
+ Font const &_font;
+
+ size_t const _opacity_values_size = 4*_font.bounding_box().count();
/**
- * Size of one cache entry in bytes
+ * Allocator wrapper that inflates each allocation with a byte padding
*/
- size_t const _alloc_size = sizeof(Cached_glyph)
- + 4*_font.bounding_box().count();
+ struct Padding_allocator : Allocator
+ {
+ size_t const _padding_bytes;
+
+ Allocator &_alloc;
+
+ size_t _consumed_bytes = 0;
+
+ size_t _padded(size_t size) const { return size + _padding_bytes; }
+
+ Padding_allocator(Allocator &alloc, size_t padding_bytes)
+ : _padding_bytes(padding_bytes), _alloc(alloc) { }
+
+ size_t consumed_bytes() const { return _consumed_bytes; }
+
+ bool alloc(size_t size, void **out_addr) override
+ {
+ size = _padded(size);
+
+ bool const result = _alloc.alloc(size, out_addr);
+
+ if (result) {
+ memset(*out_addr, 0, size);
+ _consumed_bytes += size + overhead(size);
+ }
+
+ return result;
+ }
+
+ size_t consumed() const override { return _alloc.consumed(); }
+
+ size_t overhead(size_t size) const override { return _alloc.overhead(size); };
+
+ void free(void *addr, size_t size) override
+ {
+ size = _padded(size);
+
+ _alloc.free(addr, size);
+ _consumed_bytes -= size + overhead(size);
+ }
+
+ bool need_size_for_free() const override { return _alloc.need_size_for_free(); }
+ };
+
+ Padding_allocator _padding_alloc;
+
+ typedef Lru_cache Cache;
+
+ Cache mutable _cache;
/**
- * Add cache entry for the given glyph
- *
- * \throw Out_of_ram
- * \throw Out_of_caps
+ * Return number of cache elements that fit in 'avail_bytes'
*/
- void _insert(Codepoint codepoint, Glyph const &glyph)
+ Cache::Size _cache_size(size_t const avail_bytes)
{
- auto const cached_glyph_ptr = (Cached_glyph *)_alloc.alloc(_alloc_size);
+ size_t const element_size = Cache::element_size() + _opacity_values_size;
- _stats.consumed_bytes += _alloc_size;
+ size_t const bytes_per_element = element_size + _padding_alloc.overhead(element_size);
- memset(cached_glyph_ptr, 0, _alloc_size);
-
- construct_at(cached_glyph_ptr, codepoint, glyph, _now);
-
- _avl_tree.insert(cached_glyph_ptr);
- }
-
- /**
- * Evict glyph from cache
- */
- void _remove(Cached_glyph &glyph)
- {
- _avl_tree.remove(&glyph);
-
- glyph.~Cached_glyph();
-
- _alloc.free(&glyph, _alloc_size);
-
- _stats.consumed_bytes -= _alloc_size;
- }
-
- Cached_glyph *_find_by_codepoint(Codepoint codepoint)
- {
- if (!_avl_tree.first())
- return nullptr;
-
- return _avl_tree.first()->find_by_codepoint(codepoint);
- }
-
- /**
- * Evice least recently used glyph from cache
- *
- * \return true if a glyph was released
- */
- bool _remove_least_recently_used()
- {
- if (!_avl_tree.first())
- return false;
-
- Cached_glyph *glyph = _avl_tree.first()->find_least_recently_used(_now);
- if (!glyph)
- return false; /* this should never happen */
-
- _remove(*glyph);
- _stats.misses++;
- return true;
- }
-
- void _remove_all()
- {
- while (Cached_glyph *glyph_ptr = _avl_tree.first())
- _remove(*glyph_ptr);
+ /* bytes_per_element can never be zero */
+ return Cache::Size { avail_bytes / bytes_per_element };
}
public:
@@ -210,44 +134,38 @@ class Genode::Cached_font : public Text_painter::Font
*/
Cached_font(Allocator &alloc, Font const &font, Limit limit)
:
- _alloc(alloc), _font(font), _limit(limit.value)
+ _font(font),
+ _padding_alloc(alloc, _opacity_values_size),
+ _cache(_padding_alloc, _cache_size(limit.value))
{ }
- ~Cached_font() { _remove_all(); }
+ struct Stats
+ {
+ Cache::Stats cache_stats;
+ size_t consumed_bytes;
- Stats stats() const { return _stats; }
+ void print(Output &out) const
+ {
+ Genode::print(out, "used: ", consumed_bytes/1024, " KiB, ", cache_stats);
+ }
+ };
+
+ Stats stats() const
+ {
+ return Stats { _cache.stats(), _padding_alloc.consumed_bytes() };
+ }
void _apply_glyph(Codepoint c, Apply_fn const &fn) const override
{
- _now.value++;
-
- /*
- * Try to lookup glyph from the cache. If it is missing, fill cache
- * with requested glyph and repeat the lookup. When under memory
- * pressure, flush least recently used glyphs from cache.
- *
- * Even though '_apply_glyph' is a const method, the internal cache
- * and stats must of course be mutated. Hence the 'const_cast'.
- */
- Cached_font &mutable_this = const_cast(*this);
-
- /* retry once after handling a cache miss */
- for (int i = 0; i < 2; i++) {
-
- if (Cached_glyph *glyph_ptr = mutable_this._find_by_codepoint(c)) {
- glyph_ptr->apply(fn);
- glyph_ptr->mark_as_used(_now);
- _stats.hits += (i == 0);
- return;
- }
-
- while (_stats.consumed_bytes + _alloc_size > _limit)
- if (!mutable_this._remove_least_recently_used())
- break;
+ auto hit_fn = [&] (Glyph const &glyph) { fn.apply(glyph); };
+ auto miss_fn = [&] (Cache::Missing_element &missing_element)
+ {
_font.apply_glyph(c, [&] (Glyph const &glyph) {
- mutable_this._insert(c, glyph); });
- }
+ missing_element.construct(glyph); });
+ };
+
+ (void)_cache.try_apply(c, hit_fn, miss_fn);
}
Advance_info advance_info(Codepoint c) const override
@@ -255,7 +173,6 @@ class Genode::Cached_font : public Text_painter::Font
unsigned width = 0;
Text_painter::Fixpoint_number advance { 0 };
- /* go through the '_apply_glyph' cache-fill mechanism */
Font::apply_glyph(c, [&] (Glyph const &glyph) {
width = glyph.width, advance = glyph.advance; });
@@ -267,4 +184,4 @@ class Genode::Cached_font : public Text_painter::Font
Area bounding_box() const override { return _font.bounding_box(); }
};
-#endif /* _INCLUDE__GEMS__CACHED_FONT_T_ */
+#endif /* _INCLUDE__GEMS__CACHED_FONT_H_ */
diff --git a/repos/gems/include/gems/lru_cache.h b/repos/gems/include/gems/lru_cache.h
new file mode 100644
index 000000000..3d92d0784
--- /dev/null
+++ b/repos/gems/include/gems/lru_cache.h
@@ -0,0 +1,307 @@
+/*
+ * \brief Cache with a least-recently-used eviction policy
+ * \author Norman Feske
+ * \date 2019-01-12
+ */
+
+/*
+ * Copyright (C) 2019 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#ifndef _INCLUDE__GEMS__LRU_CACHE_H_
+#define _INCLUDE__GEMS__LRU_CACHE_H_
+
+#include
+#include
+
+
+namespace Genode { template class Lru_cache; }
+
+
+template
+class Genode::Lru_cache : Noncopyable
+{
+ public:
+
+ struct Stats
+ {
+ unsigned hits, evictions;
+
+ void print(Output &out) const
+ {
+ Genode::print(out, "hits: ", hits, ", evictions: ", evictions);
+ }
+ };
+
+ private:
+
+ struct Time { unsigned value; };
+
+ Allocator &_alloc;
+ unsigned const _max_elements;
+ unsigned _used_elements = 0;
+ Time _now { 0 };
+ Stats _stats { };
+
+ class Tag
+ {
+ protected:
+
+ KEY const _key;
+
+ Time _last_used;
+
+ Tag(KEY const &key, Time now) : _key(key), _last_used(now) { }
+ };
+
+ /*
+ * The '_key' and '_last_used' attributes are supplemented as the 'Tag'
+ * base class to the 'Element'instead of being 'Element' member
+ * variables to allow 'ELEM' to be at the trailing end of the object.
+ * This way, 'ELEM' can be a variable-length type (using a flexible
+ * array member).
+ */
+
+ class Element : private Tag, private Avl_node, public ELEM
+ {
+ private:
+
+ friend class Avl_node;
+ friend class Avl_tree;
+ friend class Lru_cache;
+
+ bool _higher(KEY const &other) const
+ {
+ return Tag::_key.value > other.value;
+ }
+
+ unsigned _importance(Time now) const
+ {
+ return now.value - Tag::_last_used.value;
+ }
+
+ public:
+
+ template
+ Element(KEY key, Time now, ARGS &&... args)
+ : Tag(key, now), ELEM(args...) { }
+
+ /**
+ * Avl_node interface
+ */
+ bool higher(Element *e) { return _higher(e->_key); }
+
+ template
+ bool try_apply(KEY const &key, FN const &fn)
+ {
+ if (Tag::_key.value == key.value) {
+ fn(*this);
+ return true;
+ }
+
+ Element * const e = Avl_node::child(_higher(key));
+
+ return e && e->try_apply(key, fn);
+ }
+
+ template
+ void with_least_recently_used(Time now, FN const fn)
+ {
+ Element *result = this;
+
+ for (unsigned i = 0; i < 2; i++) {
+ Element *e = Avl_node::child(i);
+ if (e && e->_importance(now) > result->_importance(now))
+ result = e;
+ }
+
+ if (result)
+ fn(*result);
+ }
+
+ void mark_as_used(Time now) { Tag::_last_used = now; }
+ };
+
+ Avl_tree mutable _avl_tree { };
+
+ /**
+ * Add cache entry for the given key
+ *
+ * \param ARGS constructor arguments passed to new ELEM
+ *
+ * \throw Out_of_ram
+ * \throw Out_of_caps
+ */
+ template
+ void _insert(KEY key, ARGS &... args)
+ {
+ auto const element_ptr = (Element *)_alloc.alloc(sizeof(Element));
+
+ _used_elements++;
+
+ construct_at(element_ptr, key, _now, args...);
+
+ _avl_tree.insert(element_ptr);
+ }
+
+ /**
+ * Evict element from cache
+ */
+ void _remove(Element &element)
+ {
+ _avl_tree.remove(&element);
+
+ element.~Element();
+
+ _alloc.free(&element, sizeof(Element));
+
+ _used_elements--;
+ _stats.evictions++;
+ }
+
+ template
+ bool _try_apply(KEY const &key, FN const &fn)
+ {
+ if (!_avl_tree.first())
+ return false;
+
+ return _avl_tree.first()->try_apply(key, fn);
+ }
+
+ /**
+ * Evict least recently used element from cache
+ *
+ * \return true if an element was released
+ */
+ bool _remove_least_recently_used()
+ {
+ if (!_avl_tree.first())
+ return false;
+
+ _avl_tree.first()->with_least_recently_used(_now, [&] (Element &e) {
+ _remove(e); });
+
+ return true;
+ }
+
+ void _remove_all()
+ {
+ while (Element *element_ptr = _avl_tree.first())
+ _remove(*element_ptr);
+ }
+
+ public:
+
+ struct Size { size_t value; };
+
+ /**
+ * Constructor
+ *
+ * \param alloc backing store for cache elements
+ * \param size maximum number of cache elements
+ */
+ Lru_cache(Allocator &alloc, Size size)
+ : _alloc(alloc), _max_elements(size.value) { }
+
+ ~Lru_cache() { _remove_all(); }
+
+ /**
+ * Return size of a single cache entry including the meta data
+ *
+ * The returned value is useful for cache-dimensioning calculations.
+ */
+ static constexpr size_t element_size() { return sizeof(Element); }
+
+ /**
+ * Return usage stats
+ */
+ Stats stats() const { return _stats; }
+
+ /**
+ * Interface presented to the cache-miss handler to construct an
+ * element
+ */
+ class Missing_element : Noncopyable
+ {
+ private:
+
+ friend class Lru_cache;
+
+ Lru_cache &_cache;
+ KEY const &_key;
+
+ Missing_element(Lru_cache &cache, KEY const &key)
+ : _cache(cache), _key(key) { }
+
+ public:
+
+ /**
+ * Populate cache with new element
+ *
+ * \param ARGS arguments passed to the 'ELEM' constructor
+ */
+ template
+ void construct(ARGS &... args) { _cache._insert(_key, args...); }
+ };
+
+ /**
+ * Apply functor 'hit_fn' to element with matching 'key'
+ *
+ * \param miss_fn cache-miss handler
+ *
+ * \return true if 'hit_fn' got executed
+ *
+ * If the requested 'key' is not present in the cache, the cache-miss
+ * handler is called with the interface 'Missing_element &' as
+ * argument. By calling 'Missing_element::construct', the handler is
+ * able to fill the cache with the missing element. After resolving a
+ * cache miss, 'hit_fn' is called for the freshly inserted element.
+ *
+ * If an occurring cache miss is not handled, 'hit_fn' is not called.
+ */
+ template
+ bool try_apply(KEY key, HIT_FN const &hit_fn, MISS_FN const &miss_fn)
+ {
+ _now.value++;
+
+ /*
+ * Try to look up element from the cache. If it is missing, fill
+ * cache with requested element and repeat the lookup. When under
+ * memory pressure, evict the least recently used element from the
+ * cache.
+ */
+
+ /* retry once after handling a cache miss */
+ for (unsigned i = 0; i < 2; i++) {
+
+ bool const hit = _try_apply(key, [&] (Element &element) {
+ hit_fn(element);
+ element.mark_as_used(_now);
+ _stats.hits += (i == 0);
+ });
+
+ if (hit)
+ return true;
+
+ /*
+ * Handle cache miss
+ */
+
+ /* evict element if the cache is fully populated */
+ while (_used_elements >= _max_elements)
+ if (!_remove_least_recently_used())
+ break;
+
+ /* fetch missing element into the cache */
+ Missing_element missing_element { *this, key };
+ miss_fn(missing_element);
+ }
+
+ return false;
+ }
+};
+
+#endif /* _INCLUDE__GEMS__LRU_CACHE_H_ */