1312 lines
32 KiB
C
1312 lines
32 KiB
C
/*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Derived from Xenomai heapmem support (https://xenomai.org/)
|
|
* Copyright (C) 2018 Philippe Gerum <rpm@xenomai.org>
|
|
* Relicensed by its author from LGPL 2.1 to MIT.
|
|
* AVL support Copyright (c) 2015 Gilles Chanteperdrix
|
|
*/
|
|
|
|
#ifdef __OPTIMIZE__
|
|
#define NDEBUG 1
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <evl/atomic.h>
|
|
#include <evl/heap.h>
|
|
#include <evl/mutex.h>
|
|
#include <evl/clock.h>
|
|
|
|
static atomic_t heap_serial;
|
|
|
|
typedef int avlh_cmp_t(const struct avlh *const,
|
|
const struct avlh *const);
|
|
|
|
typedef struct avlh *avl_search_t(const struct avl *,
|
|
const struct avlh *, int *, int);
|
|
struct avl_searchops {
|
|
avl_search_t *search;
|
|
avlh_cmp_t *cmp;
|
|
};
|
|
|
|
#define AVL_LEFT -1
|
|
#define AVL_UP 0
|
|
#define AVL_RIGHT 1
|
|
#define avl_opposite(type) (-(type))
|
|
#define avl_type2index(type) ((type)+1)
|
|
|
|
#define AVL_THR_LEFT (1 << avl_type2index(AVL_LEFT))
|
|
#define AVL_THR_RIGHT (1 << avl_type2index(AVL_RIGHT))
|
|
|
|
#define avlh_link(avl, holder, dir) ((holder)->link[avl_type2index(dir)])
|
|
#define avl_end(avl, dir) ((avl)->end[avl_type2index(dir)])
|
|
|
|
#define avl_count(avl) ((avl)->count)
|
|
#define avl_height(avl) ((avl)->height)
|
|
#define avl_anchor(avl) (&(avl)->anchor)
|
|
|
|
#define avlh_up(avl, holder) avlh_link((avl), (holder), AVL_UP)
|
|
#define avlh_left(avl, holder) avlh_link((avl), (holder), AVL_LEFT)
|
|
#define avlh_right(avl, holder) avlh_link((avl), (holder), AVL_RIGHT)
|
|
|
|
#define avl_top(avl) (avlh_right(avl, avl_anchor(avl)))
|
|
#define avl_head(avl) (avl_end((avl), AVL_LEFT))
|
|
#define avl_tail(avl) (avl_end((avl), AVL_RIGHT))
|
|
|
|
#define avlh_thr_tst(avl, holder, side) (avlh_link(avl, holder, side) == NULL)
|
|
#define avlh_child(avl, holder, side) (avlh_link((avl),(holder),(side)))
|
|
#define avlh_has_child(avl, holder, side) (!avlh_thr_tst(avl, holder, side))
|
|
|
|
/*
|
|
* From "Bit twiddling hacks", returns v < 0 ? -1 : (v > 0 ? 1 : 0)
|
|
*/
|
|
#define avl_sign(v) \
|
|
({ \
|
|
typeof(v) _v = (v); \
|
|
((_v) > 0) - ((_v) < 0); \
|
|
})
|
|
/*
|
|
* Variation on the same theme.
|
|
*/
|
|
#define avl_cmp_sign(l, r) \
|
|
({ \
|
|
typeof(l) _l = (l); \
|
|
typeof(r) _r = (r); \
|
|
(_l > _r) - (_l < _r); \
|
|
})
|
|
|
|
#define DECLARE_AVL_SEARCH(__search_fn, __cmp) \
|
|
struct avlh *__search_fn(const struct avl *const avl, \
|
|
const struct avlh *const node, \
|
|
int *const pdelta, int dir) \
|
|
{ \
|
|
int delta = AVL_RIGHT; \
|
|
struct avlh *holder = avl_top(avl), *next; \
|
|
\
|
|
if (holder == NULL) \
|
|
goto done; \
|
|
\
|
|
for (;;) { \
|
|
delta = __cmp(node, holder); \
|
|
/* \
|
|
* Handle duplicates keys here, according to \
|
|
* "dir", if dir is: \
|
|
* - AVL_LEFT, the leftmost node is returned, \
|
|
* - AVL_RIGHT, the rightmost node is returned, \
|
|
* - 0, the first match is returned. \
|
|
*/ \
|
|
if (!(delta ?: dir)) \
|
|
break; \
|
|
next = avlh_child(avl, holder, delta ?: dir); \
|
|
if (next == NULL) \
|
|
break; \
|
|
holder = next; \
|
|
} \
|
|
\
|
|
done: \
|
|
*pdelta = delta; \
|
|
return holder; \
|
|
}
|
|
|
|
static inline unsigned int avlh_thr(const struct avl *const avl,
|
|
const struct avlh *h)
|
|
{
|
|
unsigned int result = 0;
|
|
|
|
if (avlh_link(avl, h, AVL_LEFT) == NULL)
|
|
result |= AVL_THR_LEFT;
|
|
if (avlh_link(avl, h, AVL_RIGHT) == NULL)
|
|
result |= AVL_THR_RIGHT;
|
|
|
|
return result;
|
|
}
|
|
|
|
static inline void
|
|
avlh_set_link(struct avl *const avl, struct avlh *lhs, int dir, struct avlh *rhs)
|
|
{
|
|
avlh_link(avl, lhs, dir) = rhs;
|
|
}
|
|
|
|
static inline void
|
|
avlh_set_parent_link(struct avl *const avl,
|
|
struct avlh *lhs, struct avlh *rhs)
|
|
{
|
|
avlh_set_link(avl, avlh_up(avl, lhs), lhs->type, rhs);
|
|
}
|
|
|
|
static inline void
|
|
avlh_set_left(struct avl *const avl, struct avlh *lhs,
|
|
struct avlh *rhs)
|
|
{
|
|
avlh_set_link(avl, lhs, AVL_LEFT, rhs);
|
|
}
|
|
|
|
static inline void
|
|
avlh_set_up(struct avl *const avl, struct avlh *lhs,
|
|
struct avlh *rhs)
|
|
{
|
|
avlh_set_link(avl, lhs, AVL_UP, rhs);
|
|
}
|
|
|
|
static inline void
|
|
avlh_set_right(struct avl *const avl, struct avlh* lhs,
|
|
struct avlh *rhs)
|
|
{
|
|
avlh_set_link(avl, lhs, AVL_RIGHT, rhs);
|
|
}
|
|
|
|
static inline void
|
|
avl_set_end(struct avl *const avl, int dir, struct avlh *holder)
|
|
{
|
|
avl_end(avl, dir) = holder;
|
|
}
|
|
|
|
static inline void avl_set_top(struct avl *const avl,
|
|
struct avlh *holder)
|
|
{
|
|
avlh_set_link(avl, avl_anchor(avl), AVL_RIGHT, holder);
|
|
}
|
|
|
|
static inline void avl_set_head(struct avl *const avl,
|
|
struct avlh *holder)
|
|
{
|
|
avl_set_end(avl, AVL_LEFT, holder);
|
|
}
|
|
|
|
static inline void avl_set_tail(struct avl *const avl,
|
|
struct avlh *holder)
|
|
{
|
|
avl_set_end(avl, AVL_RIGHT, holder);
|
|
}
|
|
|
|
static inline void
|
|
avlh_link_child(struct avl *const avl,
|
|
struct avlh *const oldh,
|
|
struct avlh* const newh, const int side)
|
|
{
|
|
struct avlh *const child = avlh_link(avl, oldh, side);
|
|
|
|
avlh_set_link(avl, newh, side, child);
|
|
if (avlh_has_child(avl, oldh, side))
|
|
avlh_set_up(avl, child, newh);
|
|
}
|
|
|
|
static inline void
|
|
avlh_replace(struct avl *const avl,
|
|
struct avlh *const oldh,
|
|
struct avlh *const newh)
|
|
{
|
|
newh->type = oldh->type;
|
|
/* Do not update the balance, this has to be done by the caller. */
|
|
|
|
avlh_set_up(avl, newh, avlh_up(avl, oldh));
|
|
avlh_set_parent_link(avl, oldh, newh);
|
|
|
|
avlh_link_child(avl, oldh, newh, AVL_LEFT);
|
|
avlh_link_child(avl, oldh, newh, AVL_RIGHT);
|
|
}
|
|
|
|
static struct avlh *avl_inorder(const struct avl * const avl,
|
|
struct avlh * holder,
|
|
const int dir)
|
|
{
|
|
struct avlh *next;
|
|
|
|
/*
|
|
* If the current node is not right threaded, then go down
|
|
* left, starting from its right child.
|
|
*/
|
|
if (avlh_has_child(avl, holder, dir)) {
|
|
const int opp_dir = avl_opposite(dir);
|
|
holder = avlh_link(avl, holder, dir);
|
|
while ((next = avlh_child(avl, holder, opp_dir)))
|
|
holder = next;
|
|
next = holder;
|
|
} else {
|
|
for (;;) {
|
|
next = avlh_up(avl, holder);
|
|
if (next == avl_anchor(avl))
|
|
return NULL;
|
|
if (holder->type != dir)
|
|
break;
|
|
holder = next;
|
|
}
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avl_prev(const struct avl *const avl,
|
|
struct avlh *const holder)
|
|
{
|
|
return avl_inorder(avl, holder, AVL_LEFT);
|
|
}
|
|
|
|
static inline struct avlh *
|
|
avl_search_nearest(const struct avl *const avl,
|
|
const struct avlh *node, int dir,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
struct avlh *holder;
|
|
int delta;
|
|
|
|
holder = ops->search(avl, node, &delta, 0);
|
|
if (!holder || delta != dir)
|
|
return holder;
|
|
|
|
return avl_inorder(avl, holder, dir);
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avl_search_le(const struct avl *const avl,
|
|
const struct avlh *node,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
return avl_search_nearest(avl, node, AVL_LEFT, ops);
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avl_search_ge(const struct avl *const avl,
|
|
const struct avlh *node,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
return avl_search_nearest(avl, node, AVL_RIGHT, ops);
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avl_next(const struct avl *const avl,
|
|
struct avlh *const holder)
|
|
{
|
|
return avl_inorder(avl, holder, AVL_RIGHT);
|
|
}
|
|
|
|
static void avl_delete_leaf(struct avl *const avl,
|
|
struct avlh *const node)
|
|
{
|
|
/*
|
|
* Node has no child at all. It disappears and its father
|
|
* becomes threaded on the side id was.
|
|
*/
|
|
struct avlh* const new_node = avlh_up(avl, node);
|
|
const int dir = node->type;
|
|
|
|
/* Suppress node. */
|
|
avlh_set_link(avl, new_node, dir, avlh_link(avl, node, dir));
|
|
|
|
if (node == avl_end(avl, dir))
|
|
avl_set_end(avl, dir, new_node);
|
|
}
|
|
|
|
static struct avlh *avl_delete_1child(struct avl *const avl,
|
|
struct avlh *const node, const int dir)
|
|
{
|
|
/*
|
|
* Node is threaded on one side and has a child on the other
|
|
* side. In this case, node is replaced by its child.
|
|
*/
|
|
struct avlh* const new_node = avlh_link(avl, node, dir);
|
|
|
|
/*
|
|
* Change links as if new_node was suppressed before calling
|
|
* avlh_replace.
|
|
*/
|
|
avlh_set_link(avl, node, dir, avlh_link(avl, new_node, dir));
|
|
avlh_replace(avl, node, new_node);
|
|
|
|
if (node == avl_end(avl, avl_opposite(dir)))
|
|
avl_set_end(avl, avl_opposite(dir), new_node);
|
|
/* new_node->balance 0, which is correct. */
|
|
return new_node;
|
|
}
|
|
|
|
static void avl_delete(struct avl *const avl, struct avlh *node);
|
|
|
|
static void avl_delete_2children(struct avl *const avl,
|
|
struct avlh *const node)
|
|
{
|
|
const int dir = node->balance ? node->balance : 1;
|
|
struct avlh *const new_node = avl_inorder(avl, node, dir);
|
|
|
|
avl_delete(avl, new_node);
|
|
++avl_count(avl);
|
|
avlh_replace(avl, node, new_node);
|
|
new_node->balance = node->balance;
|
|
if (avl_end(avl, dir) == node)
|
|
avl_set_end(avl, dir, new_node);
|
|
}
|
|
|
|
static inline struct avlh *avlh_rotate(struct avl *const avl,
|
|
struct avlh *const holder, const int dir)
|
|
{
|
|
const int opp_dir = avl_opposite(dir);
|
|
struct avlh *const nexttop = avlh_link(avl, holder, opp_dir);
|
|
struct avlh *const subtree = avlh_child(avl, nexttop, dir);
|
|
|
|
if (subtree) {
|
|
avlh_set_link(avl, holder, opp_dir, subtree);
|
|
avlh_set_up(avl, subtree, holder);
|
|
subtree->type = opp_dir;
|
|
} else
|
|
avlh_set_link(avl, holder, opp_dir, NULL);
|
|
|
|
avlh_set_link(avl, nexttop, dir, holder);
|
|
avlh_set_up(avl, nexttop, avlh_up(avl, holder));
|
|
nexttop->type = holder->type;
|
|
avlh_set_up(avl, holder, nexttop);
|
|
holder->type = dir;
|
|
|
|
avlh_set_parent_link(avl, nexttop, nexttop);
|
|
|
|
return nexttop;
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avlh_dbl_rotate(struct avl *const avl,
|
|
struct avlh *const holder, const int dir)
|
|
{
|
|
const int opp = avl_opposite(dir);
|
|
|
|
avlh_rotate(avl, avlh_link(avl, holder, opp), opp);
|
|
return avlh_rotate(avl, holder, dir);
|
|
}
|
|
|
|
static struct avlh *
|
|
avlh_rebalance(struct avl *const avl, struct avlh *holder,
|
|
const int delta)
|
|
{
|
|
|
|
int dir = delta;
|
|
struct avlh *const heavy_side = avlh_link(avl, holder, dir);
|
|
|
|
if (heavy_side->balance == -delta) {
|
|
/* heavy_side->balance == -delta, double rotation needed. */
|
|
holder = avlh_dbl_rotate(avl, holder, avl_opposite(dir));
|
|
|
|
/*
|
|
* recompute balances, there are three nodes involved, two of
|
|
* which balances become null.
|
|
*/
|
|
dir = holder->balance ? : AVL_RIGHT;
|
|
avlh_link(avl, holder, dir)->balance = 0;
|
|
avlh_link(avl, holder, avl_opposite(dir))->balance
|
|
= -holder->balance;
|
|
holder->balance = 0;
|
|
} else {
|
|
/*
|
|
* heavy_side->balance == delta or 0, simple rotation needed.
|
|
* the case 0 occurs only when deleting, never when inserting.
|
|
*/
|
|
|
|
/* heavy_side becomes the new root. */
|
|
avlh_rotate(avl, holder, avl_opposite(dir));
|
|
|
|
/* recompute balances. */
|
|
holder->balance -= heavy_side->balance;
|
|
heavy_side->balance -= delta;
|
|
|
|
holder = heavy_side;
|
|
}
|
|
return holder;
|
|
}
|
|
|
|
static inline
|
|
struct avlh *avlh_balance_add(struct avl *const avl,
|
|
struct avlh *const holder, const int delta)
|
|
{
|
|
if (holder->balance == delta)
|
|
/* we need to rebalance the current subtree. */
|
|
return avlh_rebalance(avl, holder, delta);
|
|
|
|
/* the current subtree does not need rebalancing */
|
|
holder->balance += delta;
|
|
return holder;
|
|
}
|
|
|
|
static void avl_delete(struct avl *const avl, struct avlh *node)
|
|
{
|
|
if (!--avl_count(avl))
|
|
goto delete_last_and_ret;
|
|
|
|
switch (avlh_thr(avl, node)) {
|
|
case (AVL_THR_LEFT | AVL_THR_RIGHT): /* thr is 5 */
|
|
avl_delete_leaf(avl, node);
|
|
break;
|
|
|
|
case AVL_THR_LEFT: /* only AVL_LEFT bit is on, thr is 1. */
|
|
node = avl_delete_1child(avl, node, AVL_RIGHT);
|
|
break;
|
|
|
|
case AVL_THR_RIGHT: /* only AVL_RIGHT bit is on, thr is 4. */
|
|
node = avl_delete_1child(avl, node, AVL_LEFT);
|
|
break;
|
|
|
|
case 0:
|
|
avl_delete_2children(avl, node);
|
|
return;
|
|
}
|
|
|
|
/* node is the first node which needs to be rebalanced.
|
|
The tree is rebalanced, and contrarily to what happened for insertion,
|
|
the rebalancing stops when a node which is NOT balanced is met. */
|
|
while (!node->balance) {
|
|
const int delta = -node->type;
|
|
node = avlh_up(avl, node);
|
|
if (node == avl_anchor(avl))
|
|
goto dec_height_and_ret;
|
|
node = avlh_balance_add(avl, node, delta);
|
|
}
|
|
|
|
return;
|
|
|
|
delete_last_and_ret:
|
|
avl_set_top(avl, NULL);
|
|
avl_set_head(avl, NULL);
|
|
avl_set_tail(avl, NULL);
|
|
dec_height_and_ret:
|
|
--avl_height(avl);
|
|
}
|
|
|
|
static inline void
|
|
avlh_attach(struct avl *const avl,
|
|
struct avlh *const parent,
|
|
struct avlh *const child, const int side)
|
|
{
|
|
avlh_set_left(avl, child, NULL);
|
|
avlh_set_right(avl, child, NULL);
|
|
avlh_set_up(avl, child, parent);
|
|
avlh_set_link(avl, parent, side, child);
|
|
child->type = side;
|
|
}
|
|
|
|
static inline void avl_insert_inner(struct avl *const avl,
|
|
struct avlh *parent,
|
|
struct avlh *const node,
|
|
const int side)
|
|
{
|
|
avlh_attach(avl, parent ? : avl_anchor(avl), node, side);
|
|
++avl_count(avl);
|
|
|
|
if (parent == NULL)
|
|
goto insert_first_and_ret; /* Get away from fast path */
|
|
|
|
if (parent == avl_end(avl, side))
|
|
avl_set_end(avl, side, node);
|
|
|
|
parent->balance += side;
|
|
|
|
while (parent->balance) {
|
|
const int delta = parent->type;
|
|
parent = avlh_up(avl, parent);
|
|
if (parent == avl_anchor(avl))
|
|
goto inc_height_and_ret; /* Get away from fast path */
|
|
parent = avlh_balance_add(avl, parent, delta);
|
|
}
|
|
|
|
return;
|
|
|
|
insert_first_and_ret:
|
|
avl_set_head(avl, node);
|
|
avl_set_tail(avl, node);
|
|
inc_height_and_ret:
|
|
++avl_height(avl);
|
|
}
|
|
|
|
static void avl_insert(struct avl *const avl,
|
|
struct avlh *const holder,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
int delta;
|
|
struct avlh *parent;
|
|
|
|
parent = ops->search(avl, holder, &delta, 0);
|
|
assert(delta != 0); /* Should not be busy. */
|
|
avl_insert_inner(avl, parent, holder, delta);
|
|
}
|
|
|
|
static void avl_insert_back(struct avl *const avl,
|
|
struct avlh *const holder,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
int delta;
|
|
struct avlh *parent;
|
|
|
|
parent = ops->search(avl, holder, &delta, AVL_RIGHT);
|
|
|
|
avl_insert_inner(avl, parent, holder, delta ? : AVL_RIGHT);
|
|
}
|
|
|
|
static void avl_prepend(struct avl *const avl,
|
|
struct avlh *const holder,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
struct avlh *const parent = avl_head(avl);
|
|
int type = parent == NULL ? AVL_RIGHT : AVL_LEFT;
|
|
|
|
assert(parent == NULL || ops->cmp(holder, parent) < 0);
|
|
avl_insert_inner(avl, parent, holder, type);
|
|
}
|
|
|
|
/*
|
|
* Special case, when we know that replacing a node with another will
|
|
* not change the avl, much faster than remove + add
|
|
*/
|
|
static void avl_replace(struct avl *avl, struct avlh *oldh,
|
|
struct avlh *newh,
|
|
const struct avl_searchops *ops)
|
|
{
|
|
struct avlh *prev __maybe_unused, *next __maybe_unused;
|
|
|
|
prev = avl_prev(avl, oldh);
|
|
next = avl_next(avl, oldh);
|
|
|
|
assert(!((prev && ops->cmp(newh, prev) < 0)
|
|
|| (next && ops->cmp(newh, next) > 0)));
|
|
|
|
avlh_replace(avl, oldh, newh);
|
|
if (oldh == avl_head(avl))
|
|
avl_set_head(avl, newh);
|
|
if (oldh == avl_tail(avl))
|
|
avl_set_tail(avl, newh);
|
|
newh->balance = oldh->balance;
|
|
}
|
|
|
|
static inline void avlh_init(struct avlh *const holder)
|
|
{
|
|
holder->balance = 0;
|
|
holder->type = 0;
|
|
}
|
|
|
|
static void avl_init(struct avl *const avl)
|
|
{
|
|
avlh_init(avl_anchor(avl)); /* this must be first. */
|
|
avl_height(avl) = 0;
|
|
avl_count(avl) = 0;
|
|
avl_set_top(avl, NULL);
|
|
avl_set_head(avl, NULL);
|
|
avl_set_tail(avl, NULL);
|
|
}
|
|
|
|
enum evl_heap_pgtype {
|
|
page_free =0,
|
|
page_cont =1,
|
|
page_list =2
|
|
};
|
|
|
|
static struct avl_searchops size_search_ops;
|
|
static struct avl_searchops addr_search_ops;
|
|
|
|
static inline uint32_t __attribute__ ((always_inline))
|
|
gen_block_mask(int log2size)
|
|
{
|
|
return -1U >> (32 - (EVL_HEAP_PAGE_SIZE >> log2size));
|
|
}
|
|
|
|
static inline __attribute__ ((always_inline))
|
|
int addr_to_pagenr(struct evl_heap_extent *ext, void *p)
|
|
{
|
|
return ((void *)p - ext->membase) >> EVL_HEAP_PAGE_SHIFT;
|
|
}
|
|
|
|
static inline __attribute__ ((always_inline))
|
|
void *pagenr_to_addr(struct evl_heap_extent *ext, int pg)
|
|
{
|
|
return ext->membase + (pg << EVL_HEAP_PAGE_SHIFT);
|
|
}
|
|
|
|
#ifndef __OPTIMIZE__
|
|
/*
|
|
* Setting page_cont/page_free in the page map is only required for
|
|
* enabling full checking of the block address in free requests, which
|
|
* may be extremely time-consuming when deallocating huge blocks
|
|
* spanning thousands of pages. We only do such marking when running
|
|
* in full debug mode.
|
|
*/
|
|
static inline bool
|
|
page_is_valid(struct evl_heap_extent *ext, int pg)
|
|
{
|
|
switch (ext->pagemap[pg].type) {
|
|
case page_free:
|
|
case page_cont:
|
|
return false;
|
|
case page_list:
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static void mark_pages(struct evl_heap_extent *ext,
|
|
int pg, int nrpages,
|
|
enum evl_heap_pgtype type)
|
|
{
|
|
while (nrpages-- > 0)
|
|
ext->pagemap[pg].type = type;
|
|
}
|
|
|
|
#else
|
|
|
|
static inline bool
|
|
page_is_valid(struct evl_heap_extent *ext, int pg)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static void mark_pages(struct evl_heap_extent *ext,
|
|
int pg, int nrpages,
|
|
enum evl_heap_pgtype type)
|
|
{ }
|
|
|
|
#endif
|
|
|
|
ssize_t evl_check_block(struct evl_heap *heap, void *block)
|
|
{
|
|
unsigned long pg, pgoff, boff;
|
|
struct evl_heap_extent *ext;
|
|
ssize_t ret = -EINVAL;
|
|
size_t bsize;
|
|
|
|
evl_lock_mutex(&heap->lock);
|
|
|
|
/*
|
|
* Find the extent the checked block is originating from.
|
|
*/
|
|
list_for_each_entry(ext, &heap->extents, next) {
|
|
if (block >= ext->membase &&
|
|
block < ext->memlim)
|
|
goto found;
|
|
}
|
|
goto out;
|
|
found:
|
|
/* Calculate the page number from the block address. */
|
|
pgoff = block - ext->membase;
|
|
pg = pgoff >> EVL_HEAP_PAGE_SHIFT;
|
|
if (page_is_valid(ext, pg)) {
|
|
if (ext->pagemap[pg].type == page_list)
|
|
bsize = ext->pagemap[pg].bsize;
|
|
else {
|
|
bsize = (1 << ext->pagemap[pg].type);
|
|
boff = pgoff & ~EVL_HEAP_PAGE_MASK;
|
|
if ((boff & (bsize - 1)) != 0) /* Not at block start? */
|
|
goto out;
|
|
}
|
|
ret = (ssize_t)bsize;
|
|
}
|
|
out:
|
|
evl_unlock_mutex(&heap->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline struct evl_mem_range *
|
|
find_suitable_range(struct evl_heap_extent *ext, size_t size)
|
|
{
|
|
struct evl_mem_range lookup;
|
|
struct avlh *node;
|
|
|
|
lookup.size = size;
|
|
node = avl_search_ge(&ext->size_tree, &lookup.size_node,
|
|
&size_search_ops);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
return container_of(node, struct evl_mem_range, size_node);
|
|
}
|
|
|
|
static int reserve_page_range(struct evl_heap_extent *ext, size_t size)
|
|
{
|
|
struct evl_mem_range *new, *splitr;
|
|
|
|
new = find_suitable_range(ext, size);
|
|
if (new == NULL)
|
|
return -1;
|
|
|
|
avl_delete(&ext->size_tree, &new->size_node);
|
|
if (new->size == size) {
|
|
avl_delete(&ext->addr_tree, &new->addr_node);
|
|
return addr_to_pagenr(ext, new);
|
|
}
|
|
|
|
/*
|
|
* The free range fetched is larger than what we need: split
|
|
* it in two, the upper part goes to the user, the lower part
|
|
* is returned to the free list, which makes reindexing by
|
|
* address pointless.
|
|
*/
|
|
splitr = new;
|
|
splitr->size -= size;
|
|
new = (struct evl_mem_range *)((void *)new + splitr->size);
|
|
avlh_init(&splitr->size_node);
|
|
avl_insert_back(&ext->size_tree, &splitr->size_node,
|
|
&size_search_ops);
|
|
|
|
return addr_to_pagenr(ext, new);
|
|
}
|
|
|
|
static inline struct evl_mem_range *
|
|
find_left_neighbour(struct evl_heap_extent *ext, struct evl_mem_range *r)
|
|
{
|
|
struct avlh *node;
|
|
|
|
node = avl_search_le(&ext->addr_tree, &r->addr_node,
|
|
&addr_search_ops);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
return container_of(node, struct evl_mem_range, addr_node);
|
|
}
|
|
|
|
static inline struct evl_mem_range *
|
|
find_right_neighbour(struct evl_heap_extent *ext, struct evl_mem_range *r)
|
|
{
|
|
struct avlh *node;
|
|
|
|
node = avl_search_ge(&ext->addr_tree, &r->addr_node,
|
|
&addr_search_ops);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
return container_of(node, struct evl_mem_range, addr_node);
|
|
}
|
|
|
|
static inline struct evl_mem_range *
|
|
find_next_neighbour(struct evl_heap_extent *ext, struct evl_mem_range *r)
|
|
{
|
|
struct avlh *node;
|
|
|
|
node = avl_next(&ext->addr_tree, &r->addr_node);
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
return container_of(node, struct evl_mem_range, addr_node);
|
|
}
|
|
|
|
static inline bool
|
|
ranges_mergeable(struct evl_mem_range *left, struct evl_mem_range *right)
|
|
{
|
|
return (void *)left + left->size == (void *)right;
|
|
}
|
|
|
|
static void release_page_range(struct evl_heap_extent *ext,
|
|
void *page, size_t size)
|
|
{
|
|
struct evl_mem_range *freed = page, *left, *right;
|
|
bool addr_linked = false;
|
|
|
|
freed->size = size;
|
|
|
|
left = find_left_neighbour(ext, freed);
|
|
if (left && ranges_mergeable(left, freed)) {
|
|
avl_delete(&ext->size_tree, &left->size_node);
|
|
left->size += freed->size;
|
|
freed = left;
|
|
addr_linked = true;
|
|
right = find_next_neighbour(ext, freed);
|
|
} else
|
|
right = find_right_neighbour(ext, freed);
|
|
|
|
if (right && ranges_mergeable(freed, right)) {
|
|
avl_delete(&ext->size_tree, &right->size_node);
|
|
freed->size += right->size;
|
|
if (addr_linked)
|
|
avl_delete(&ext->addr_tree, &right->addr_node);
|
|
else
|
|
avl_replace(&ext->addr_tree, &right->addr_node,
|
|
&freed->addr_node, &addr_search_ops);
|
|
} else if (!addr_linked) {
|
|
avlh_init(&freed->addr_node);
|
|
if (left)
|
|
avl_insert(&ext->addr_tree, &freed->addr_node,
|
|
&addr_search_ops);
|
|
else
|
|
avl_prepend(&ext->addr_tree, &freed->addr_node,
|
|
&addr_search_ops);
|
|
}
|
|
|
|
avlh_init(&freed->size_node);
|
|
avl_insert_back(&ext->size_tree, &freed->size_node,
|
|
&size_search_ops);
|
|
mark_pages(ext, addr_to_pagenr(ext, page),
|
|
size >> EVL_HEAP_PAGE_SHIFT, page_free);
|
|
}
|
|
|
|
static void add_page_front(struct evl_heap *heap,
|
|
struct evl_heap_extent *ext,
|
|
int pg, int log2size)
|
|
{
|
|
struct evl_heap_pgentry *new, *head, *next;
|
|
int ilog;
|
|
|
|
/* Insert page at front of the per-bucket page list. */
|
|
|
|
ilog = log2size - EVL_HEAP_MIN_LOG2;
|
|
new = &ext->pagemap[pg];
|
|
if (heap->buckets[ilog] == -1U) {
|
|
heap->buckets[ilog] = pg;
|
|
new->prev = new->next = pg;
|
|
} else {
|
|
head = &ext->pagemap[heap->buckets[ilog]];
|
|
new->prev = heap->buckets[ilog];
|
|
new->next = head->next;
|
|
next = &ext->pagemap[new->next];
|
|
next->prev = pg;
|
|
head->next = pg;
|
|
heap->buckets[ilog] = pg;
|
|
}
|
|
}
|
|
|
|
static void remove_page(struct evl_heap *heap,
|
|
struct evl_heap_extent *ext,
|
|
int pg, int log2size)
|
|
{
|
|
struct evl_heap_pgentry *old, *prev, *next;
|
|
int ilog = log2size - EVL_HEAP_MIN_LOG2;
|
|
|
|
/* Remove page from the per-bucket page list. */
|
|
|
|
old = &ext->pagemap[pg];
|
|
if (pg == old->next)
|
|
heap->buckets[ilog] = -1U;
|
|
else {
|
|
if (pg == heap->buckets[ilog])
|
|
heap->buckets[ilog] = old->next;
|
|
prev = &ext->pagemap[old->prev];
|
|
prev->next = old->next;
|
|
next = &ext->pagemap[old->next];
|
|
next->prev = old->prev;
|
|
}
|
|
}
|
|
|
|
static void move_page_front(struct evl_heap *heap,
|
|
struct evl_heap_extent *ext,
|
|
int pg, int log2size)
|
|
{
|
|
int ilog = log2size - EVL_HEAP_MIN_LOG2;
|
|
|
|
/* Move page at front of the per-bucket page list. */
|
|
|
|
if (heap->buckets[ilog] == pg)
|
|
return; /* Already at front, no move. */
|
|
|
|
remove_page(heap, ext, pg, log2size);
|
|
add_page_front(heap, ext, pg, log2size);
|
|
}
|
|
|
|
static void move_page_back(struct evl_heap *heap,
|
|
struct evl_heap_extent *ext,
|
|
int pg, int log2size)
|
|
{
|
|
struct evl_heap_pgentry *old, *last, *head, *next;
|
|
int ilog;
|
|
|
|
/* Move page at end of the per-bucket page list. */
|
|
|
|
old = &ext->pagemap[pg];
|
|
if (pg == old->next) /* Singleton, no move. */
|
|
return;
|
|
|
|
remove_page(heap, ext, pg, log2size);
|
|
|
|
ilog = log2size - EVL_HEAP_MIN_LOG2;
|
|
head = &ext->pagemap[heap->buckets[ilog]];
|
|
last = &ext->pagemap[head->prev];
|
|
old->prev = head->prev;
|
|
old->next = last->next;
|
|
next = &ext->pagemap[old->next];
|
|
next->prev = pg;
|
|
last->next = pg;
|
|
}
|
|
|
|
static void *add_free_range(struct evl_heap *heap, size_t bsize, int log2size)
|
|
{
|
|
struct evl_heap_extent *ext;
|
|
size_t rsize;
|
|
int pg;
|
|
|
|
/*
|
|
* Scanning each extent, search for a range of contiguous
|
|
* pages in the extent. The range must be at least @bsize
|
|
* long. @pg is the heading page number on success.
|
|
*/
|
|
rsize =__align_to(bsize, EVL_HEAP_PAGE_SIZE);
|
|
list_for_each_entry(ext, &heap->extents, next) {
|
|
pg = reserve_page_range(ext, rsize);
|
|
if (pg >= 0)
|
|
goto found;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
found:
|
|
/*
|
|
* Update the page entry. If @log2size is non-zero
|
|
* (i.e. bsize < EVL_HEAP_PAGE_SIZE), bsize is (1 << log2Size)
|
|
* between 2^EVL_HEAP_MIN_LOG2 and 2^(EVL_HEAP_PAGE_SHIFT - 1).
|
|
* Save the log2 power into entry.type, then update the
|
|
* per-page allocation bitmap to reserve the first block.
|
|
*
|
|
* Otherwise, we have a larger block which may span multiple
|
|
* pages: set entry.type to page_list, indicating the start of
|
|
* the page range, and entry.bsize to the overall block size.
|
|
*/
|
|
if (log2size) {
|
|
ext->pagemap[pg].type = log2size;
|
|
/*
|
|
* Mark the first object slot (#0) as busy, along with
|
|
* the leftmost bits we won't use for this log2 size.
|
|
*/
|
|
ext->pagemap[pg].map = ~gen_block_mask(log2size) | 1;
|
|
/*
|
|
* Insert the new page at front of the per-bucket page
|
|
* list, enforcing the assumption that pages with free
|
|
* space live close to the head of this list.
|
|
*/
|
|
add_page_front(heap, ext, pg, log2size);
|
|
} else {
|
|
ext->pagemap[pg].type = page_list;
|
|
ext->pagemap[pg].bsize = (uint32_t)bsize;
|
|
mark_pages(ext, pg + 1,
|
|
(bsize >> EVL_HEAP_PAGE_SHIFT) - 1, page_cont);
|
|
}
|
|
|
|
heap->used_size += bsize;
|
|
|
|
return pagenr_to_addr(ext, pg);
|
|
}
|
|
|
|
void *evl_alloc_block(struct evl_heap *heap, size_t size)
|
|
{
|
|
struct evl_heap_extent *ext;
|
|
int log2size, ilog, pg, b;
|
|
uint32_t bmask;
|
|
size_t bsize;
|
|
void *block;
|
|
|
|
if (size == 0)
|
|
return NULL;
|
|
|
|
if (size < EVL_HEAP_MIN_ALIGN) {
|
|
bsize = size = EVL_HEAP_MIN_ALIGN;
|
|
log2size = EVL_HEAP_MIN_LOG2;
|
|
} else {
|
|
log2size = sizeof(size) * CHAR_BIT - 1 -
|
|
__lzcount(size);
|
|
if (log2size < EVL_HEAP_PAGE_SHIFT) {
|
|
if (size & (size - 1))
|
|
log2size++;
|
|
bsize = 1 << log2size;
|
|
} else
|
|
bsize = __align_to(size, EVL_HEAP_PAGE_SIZE);
|
|
}
|
|
|
|
/*
|
|
* Allocate entire pages directly from the pool whenever the
|
|
* block is larger or equal to EVL_HEAP_PAGE_SIZE. Otherwise,
|
|
* use bucketed memory.
|
|
*
|
|
* NOTE: Fully busy pages from bucketed memory are moved back
|
|
* at the end of the per-bucket page list, so that we may
|
|
* always assume that either the heading page has some room
|
|
* available, or no room is available from any page linked to
|
|
* this list, in which case we should immediately add a fresh
|
|
* page.
|
|
*/
|
|
if (bsize < EVL_HEAP_PAGE_SIZE) {
|
|
ilog = log2size - EVL_HEAP_MIN_LOG2;
|
|
assert(ilog >= 0 && ilog < EVL_HEAP_MAX_BUCKETS);
|
|
|
|
evl_lock_mutex(&heap->lock);
|
|
|
|
list_for_each_entry(ext, &heap->extents, next) {
|
|
pg = heap->buckets[ilog];
|
|
if (pg < 0) /* Empty page list? */
|
|
continue;
|
|
|
|
/*
|
|
* Find a block in the heading page. If there
|
|
* is none, there won't be any down the list:
|
|
* add a new page right away.
|
|
*/
|
|
bmask = ext->pagemap[pg].map;
|
|
if (bmask == -1U)
|
|
break;
|
|
b = __tzcount(~bmask);
|
|
|
|
/*
|
|
* Got one block from the heading per-bucket
|
|
* page, tag it as busy in the per-page
|
|
* allocation map.
|
|
*/
|
|
ext->pagemap[pg].map |= (1U << b);
|
|
heap->used_size += bsize;
|
|
block = ext->membase +
|
|
(pg << EVL_HEAP_PAGE_SHIFT) +
|
|
(b << log2size);
|
|
if (ext->pagemap[pg].map == -1U)
|
|
move_page_back(heap, ext, pg, log2size);
|
|
goto out;
|
|
}
|
|
|
|
/* No free block in bucketed memory, add one page. */
|
|
block = add_free_range(heap, bsize, log2size);
|
|
} else {
|
|
evl_lock_mutex(&heap->lock);
|
|
/* Add a range of contiguous free pages. */
|
|
block = add_free_range(heap, bsize, 0);
|
|
}
|
|
out:
|
|
evl_unlock_mutex(&heap->lock);
|
|
|
|
return block;
|
|
}
|
|
|
|
int evl_free_block(struct evl_heap *heap, void *block)
|
|
{
|
|
int log2size, ret = 0, pg, n;
|
|
struct evl_heap_extent *ext;
|
|
unsigned long pgoff, boff;
|
|
uint32_t oldmap;
|
|
size_t bsize;
|
|
|
|
evl_lock_mutex(&heap->lock);
|
|
|
|
/*
|
|
* Find the extent from which the returned block is
|
|
* originating from.
|
|
*/
|
|
list_for_each_entry(ext, &heap->extents, next) {
|
|
if (block >= ext->membase && block < ext->memlim)
|
|
goto found;
|
|
}
|
|
|
|
goto bad;
|
|
found:
|
|
/* Compute the heading page number in the page map. */
|
|
pgoff = block - ext->membase;
|
|
pg = pgoff >> EVL_HEAP_PAGE_SHIFT;
|
|
if (!page_is_valid(ext, pg))
|
|
goto bad;
|
|
|
|
switch (ext->pagemap[pg].type) {
|
|
case page_list:
|
|
bsize = ext->pagemap[pg].bsize;
|
|
assert((bsize & (EVL_HEAP_PAGE_SIZE - 1)) == 0);
|
|
release_page_range(ext, pagenr_to_addr(ext, pg), bsize);
|
|
break;
|
|
|
|
default:
|
|
log2size = ext->pagemap[pg].type;
|
|
bsize = (1 << log2size);
|
|
assert(bsize < EVL_HEAP_PAGE_SIZE);
|
|
boff = pgoff & ~EVL_HEAP_PAGE_MASK;
|
|
if ((boff & (bsize - 1)) != 0) /* Not at block start? */
|
|
goto bad;
|
|
|
|
n = boff >> log2size; /* Block position in page. */
|
|
oldmap = ext->pagemap[pg].map;
|
|
ext->pagemap[pg].map &= ~(1U << n);
|
|
|
|
/*
|
|
* If the page the block was sitting on is fully idle,
|
|
* return it to the pool. Otherwise, check whether
|
|
* that page is transitioning from fully busy to
|
|
* partially busy state, in which case it should move
|
|
* toward the front of the per-bucket page list.
|
|
*/
|
|
if (ext->pagemap[pg].map == ~gen_block_mask(log2size)) {
|
|
remove_page(heap, ext, pg, log2size);
|
|
release_page_range(ext, pagenr_to_addr(ext, pg),
|
|
EVL_HEAP_PAGE_SIZE);
|
|
} else if (oldmap == -1U)
|
|
move_page_front(heap, ext, pg, log2size);
|
|
}
|
|
|
|
heap->used_size -= bsize;
|
|
out:
|
|
evl_unlock_mutex(&heap->lock);
|
|
|
|
return ret;
|
|
bad:
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
static inline int compare_range_by_size(const struct avlh *l, const struct avlh *r)
|
|
{
|
|
struct evl_mem_range *rl = container_of(l, typeof(*rl), size_node);
|
|
struct evl_mem_range *rr = container_of(r, typeof(*rl), size_node);
|
|
|
|
return avl_sign((long)(rl->size - rr->size));
|
|
}
|
|
static DECLARE_AVL_SEARCH(search_range_by_size, compare_range_by_size);
|
|
|
|
static struct avl_searchops size_search_ops = {
|
|
.search = search_range_by_size,
|
|
.cmp = compare_range_by_size,
|
|
};
|
|
|
|
static inline int compare_range_by_addr(const struct avlh *l, const struct avlh *r)
|
|
{
|
|
uintptr_t al = (uintptr_t)l, ar = (uintptr_t)r;
|
|
|
|
return avl_cmp_sign(al, ar);
|
|
}
|
|
static DECLARE_AVL_SEARCH(search_range_by_addr, compare_range_by_addr);
|
|
|
|
static struct avl_searchops addr_search_ops = {
|
|
.search = search_range_by_addr,
|
|
.cmp = compare_range_by_addr,
|
|
};
|
|
|
|
static ssize_t add_extent(void *mem, size_t size)
|
|
{
|
|
struct evl_heap_extent *ext;
|
|
size_t user_size, overhead;
|
|
int nrpages;
|
|
|
|
/*
|
|
* @size must include the overhead memory we need for storing
|
|
* our meta-data as calculated by EVL_HEAP_RAW_SIZE(), find
|
|
* this amount back.
|
|
*
|
|
* o = overhead
|
|
* e = sizeof(evl_heap_extent)
|
|
* p = EVL_HEAP_PAGE_SIZE
|
|
* m = EVL_HEAP_PGMAP_BYTES
|
|
*
|
|
* o = align_to(((a * m + e * p) / (p + m)), minlog2)
|
|
*/
|
|
overhead = __align_to((size * EVL_HEAP_PGMAP_BYTES +
|
|
sizeof(*ext) * EVL_HEAP_PAGE_SIZE) /
|
|
(EVL_HEAP_PAGE_SIZE + EVL_HEAP_PGMAP_BYTES),
|
|
EVL_HEAP_MIN_ALIGN);
|
|
|
|
user_size = size - overhead;
|
|
if (user_size & ~EVL_HEAP_PAGE_MASK)
|
|
return -EINVAL;
|
|
|
|
if (user_size < EVL_HEAP_PAGE_SIZE ||
|
|
user_size > EVL_HEAP_MAX_EXTSZ)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Setup an extent covering user_size bytes of user memory
|
|
* starting at @mem. user_size must be a multiple of
|
|
* EVL_HEAP_PAGE_SIZE. The extent starts with a descriptor,
|
|
* followed by the array of page entries.
|
|
*
|
|
* Page entries contain per-page metadata for managing the
|
|
* page pool.
|
|
*
|
|
* +-------------------+ <= mem
|
|
* | extent descriptor |
|
|
* /...................\
|
|
* \...page entries[]../
|
|
* /...................\
|
|
* +-------------------+ <= extent->membase
|
|
* | |
|
|
* | |
|
|
* | (page pool) |
|
|
* | |
|
|
* | |
|
|
* +-------------------+
|
|
* <= extent->memlim == mem + size
|
|
*/
|
|
nrpages = user_size >> EVL_HEAP_PAGE_SHIFT;
|
|
ext = mem;
|
|
ext->membase = mem + overhead;
|
|
ext->memlim = mem + size;
|
|
|
|
memset(ext->pagemap, 0, nrpages * sizeof(struct evl_heap_pgentry));
|
|
/*
|
|
* The free page pool is maintained as a set of ranges of
|
|
* contiguous pages indexed by address and size in AVL
|
|
* trees. Initially, we have a single range in those trees
|
|
* covering the whole user memory we have been given for the
|
|
* extent. Over time, that range will be split then possibly
|
|
* re-merged back as allocations and deallocations take place.
|
|
*/
|
|
avl_init(&ext->size_tree);
|
|
avl_init(&ext->addr_tree);
|
|
release_page_range(ext, ext->membase, user_size);
|
|
|
|
return (ssize_t)user_size;
|
|
}
|
|
|
|
int evl_init_heap(struct evl_heap *heap, void *mem, size_t size)
|
|
{
|
|
struct evl_heap_extent *ext = mem;
|
|
ssize_t ret;
|
|
int n;
|
|
|
|
list_init(&heap->extents);
|
|
|
|
ret = evl_new_mutex(&heap->lock, "heap:%.3d",
|
|
atomic_add_return(&heap_serial, 1));
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* Reset bucket page lists, all empty. */
|
|
for (n = 0; n < EVL_HEAP_MAX_BUCKETS; n++)
|
|
heap->buckets[n] = -1U;
|
|
|
|
ret = add_extent(mem, size);
|
|
if (ret < 0) {
|
|
evl_close_mutex(&heap->lock);
|
|
return ret;
|
|
}
|
|
|
|
list_append(&ext->next, &heap->extents);
|
|
heap->raw_size = size;
|
|
heap->usable_size = ret;
|
|
heap->used_size = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int evl_extend_heap(struct evl_heap *heap, void *mem, size_t size)
|
|
{
|
|
struct evl_heap_extent *ext = mem;
|
|
ssize_t ret;
|
|
|
|
ret = add_extent(mem, size);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
evl_lock_mutex(&heap->lock);
|
|
list_append(&ext->next, &heap->extents);
|
|
heap->raw_size += size;
|
|
heap->usable_size += ret;
|
|
evl_unlock_mutex(&heap->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t evl_heap_raw_size(const struct evl_heap *heap)
|
|
{
|
|
return heap->raw_size;
|
|
}
|
|
|
|
size_t evl_heap_size(const struct evl_heap *heap)
|
|
{
|
|
return heap->usable_size;
|
|
}
|
|
|
|
size_t evl_heap_used(const struct evl_heap *heap)
|
|
{
|
|
return heap->used_size;
|
|
}
|
|
|
|
void evl_destroy_heap(struct evl_heap *heap)
|
|
{
|
|
evl_close_mutex(&heap->lock);
|
|
}
|