Port B+TREE to new memory model

This commit is contained in:
Patrick Pelissier committed 2025-05-07 21:02:23 +02:00
1 parent 6e828eabaf
commit bcc9f2c894
2 files changed
+85 -81

No files matched your search

+71 -71
View File
@@ -343,6 +343,7 @@
M_BPTR33_DEF_IT(name, N, key_t, key_oplist, value_t, value_oplist, isMap, isMulti, tree_t, node_t, pit_t, it_t, subtype_t) \
M_BPTR33_DEF_EXT(name, N, key_t, key_oplist, value_t, value_oplist, isMap, isMulti, tree_t, node_t, pit_t, it_t, subtype_t) \
M_EMPLACE_ASS_ARRAY_OR_QUEUE_DEF(M_INV(isMap), name, tree_t, key_oplist, value_oplist)
/* TODO: Check if key type has not disabled INIT_MOVE */
/* Define the types of a B+Tree */
#define M_BPTR33_DEF_TYPE(name, N, key_t, key_oplist, value_t, value_oplist, isMap, isMulti, tree_t, node_t, pit_t, it_t, subtype_t) \
@@ -406,7 +407,7 @@
\
/* Allocate a new node */ \
/* TODO: Can be specialized to alloc for leaf or for non leaf */ \
M_INLINE node_t M_F(name, _new_node)(void) \
M_INLINE node_t M_F(name, _new_node)(M_P_EXPAND_void) \
{ \
M_STATIC_ASSERT(N >= 2, M_LIB_ILLEGAL_PARAM, \
"Number of items per node shall be >= 2."); \
@@ -420,9 +421,9 @@
return n; \
} \
\
M_INLINE void M_F(name, _init)(tree_t b) \
M_P(void, name, _init, tree_t b) \
{ \
b->root = M_F(name, _new_node)(); \
b->root = M_F(name, _new_node)(M_R_EXPAND_void); \
b->size = 0; \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
} \
@@ -443,7 +444,7 @@
return num; \
} \
\
M_INLINE void M_F(name, _reset)(tree_t b) \
M_P(void, name, _reset, tree_t b) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
node_t next, n = b->root; \
@@ -486,19 +487,19 @@
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
} \
\
M_INLINE void M_F(name, _clear)(tree_t b) \
M_P(void, name, _clear, tree_t b) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
M_F(name, _reset)(b); \
M_F(name, _reset)M_R(b); \
/* Once the tree is clean, only the root remains */ \
M_CALL_DEL(key_oplist, b->root); \
b->root = NULL; \
} \
\
/* Copy recursively the node 'o' of root node 'root' */ \
M_INLINE node_t M_F(name, _copy_node)(const node_t o, const node_t root) \
M_P(node_t, name, _copy_node, const node_t o, const node_t root) \
{ \
node_t n = M_F(name, _new_node)(); \
node_t n = M_F(name, _new_node)(M_R_EXPAND_void); \
/* Set default number of keys and type to copy */ \
n->num = o->num; \
/* By default it is not linked to its brother. \
@@ -520,7 +521,7 @@
/* Copy recursively the associated nodes if it is not a leaf */ \
for(int i = 0; i <= num; i++) { \
M_ASSERT(o->kind.node[i] != root); \
n->kind.node[i] = M_F(name, _copy_node)(o->kind.node[i], root); \
n->kind.node[i] = M_F(name, _copy_node)M_R(o->kind.node[i], root); \
} \
/* The copied nodes don't have their next field correct */ \
/* Fix the next field for the copied nodes */ \
@@ -542,22 +543,22 @@
return n; \
} \
\
M_INLINE void M_F(name, _init_set)(tree_t b, const tree_t o) \
M_P(void, name, _init_set, tree_t b, const tree_t o) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, o); \
M_ASSERT (b != NULL); \
/* Just copy recursively the root node */ \
b->root = M_F(name, _copy_node)(o->root, o->root); \
b->root = M_F(name, _copy_node)M_R(o->root, o->root); \
b->size = o->size; \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
} \
\
M_INLINE void M_F(name, _set)(tree_t b, const tree_t o) \
M_P(void, name, _set, tree_t b, const tree_t o) \
{ \
/* NOTE: We could reuse the already allocated nodes of 'b'. \
Not sure if it worth the effort */ \
M_F(name, _clear)(b); \
M_F(name, _init_set)(b, o); \
M_F(name, _clear)M_R(b); \
M_F(name, _init_set)M_R(b, o); \
} \
\
M_INLINE bool M_F(name, _empty_p)(const tree_t b) \
@@ -573,7 +574,7 @@
return b->size; \
} \
\
M_INLINE node_t M_F(name, _search_for_leaf)(pit_t pit, const tree_t b, key_t const key) \
M_INLINE node_t M_F(name, _i_search_for_leaf)(pit_t pit, const tree_t b, key_t const key) \
{ \
node_t n = b->root; \
int np = 0; \
@@ -607,7 +608,7 @@
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
pit_t pit; \
/* Get the leaf node where the key can be */ \
node_t n = M_F(name, _search_for_leaf)(pit, b, key); \
node_t n = M_F(name, _i_search_for_leaf)(pit, b, key); \
int cmp = 0; \
M_BPTR33_NODE_CONTRACT(N, isMulti, key_oplist, n, b->root); \
/* Search in the leaf for key */ \
@@ -627,11 +628,11 @@
return M_CONST_CAST(value_t, M_F(name, _get)(b, key)); \
} \
\
M_INLINE int \
M_F(name, _search_and_insert_in_leaf)(node_t n, key_t const key \
M_P(int, name, _i_search_and_insert_in_leaf, node_t n, key_t const key \
M_IF(isMap)( M_DEFERRED_COMMA value_t const value,) ) \
{ \
M_ASSERT (M_F(name, _is_leaf)(n)); \
M_ASSERT_POOL(); \
int i, num = M_F(name, _get_num)(n); \
M_ASSERT (num <= N); \
/* Search for the key in the node n (a leaf) for insertion */ \
@@ -659,10 +660,10 @@
return i; \
} \
\
M_INLINE int \
M_F(name, _search_and_insert_in_node)(node_t n, node_t l, key_t key) \
M_P(int, name, _i_search_and_insert_in_node, node_t n, node_t l, key_t key) \
{ \
M_ASSERT (!M_F(name, _is_leaf)(n)); \
M_ASSERT_POOL(); \
int i, num = M_F(name, _get_num)(n); \
M_ASSERT (num <= N); \
/* Search for the key in the node n (not a leaf) for insertion */ \
@@ -682,16 +683,16 @@
return i; \
} \
\
M_INLINE void \
M_IF(isMap)(M_F(name, _set_at),M_F(name,_push))(tree_t b, key_t const key \
M_IF(isMap)(M_DEFERRED_COMMA value_t const value,)) \
M_IF(isMap)( \
M_P(void, name, _set_at, tree_t b, key_t const key, value_t const value) , \
M_P(void, name, _push, tree_t b, key_t const key) ) \
{ \
pit_t pit; \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
node_t leaf = M_F(name, _search_for_leaf)(pit, b, key); \
node_t leaf = M_F(name, _i_search_for_leaf)(pit, b, key); \
/* Insert key into the leaf.*/ \
/* NOTE: Even if there is N elements, we can still add one more.*/ \
int i = M_F(name, _search_and_insert_in_leaf)(leaf, key M_IF (isMap)(M_DEFERRED_COMMA value,)); \
int i = M_F(name, _i_search_and_insert_in_leaf)M_R(leaf, key M_IF (isMap)(M_DEFERRED_COMMA value,)); \
if (i < 0) { \
/* Nothing to do anymore. key already exists in the tree. \
value has been updated if needed */ \
@@ -713,7 +714,7 @@
/* leaf is full: need to slip the leaf in two */ \
int nnum = (N + 1) / 2; \
num = N + 1 - nnum; \
node_t nleaf = M_F(name, _new_node)(); \
node_t nleaf = M_F(name, _new_node)(M_R_EXPAND_void); \
/* Move half objects to the new node */ \
memmove(&nleaf->key[0], &leaf->key[num], sizeof(key_t)*(unsigned int)nnum); \
M_IF(isMap)(memmove(&nleaf->kind.value[0], &leaf->kind.value[num], sizeof(value_t)*(unsigned int)nnum);,) \
@@ -728,7 +729,7 @@
while (true) { \
if (pit->num == 0) { \
/* We reach root ==> Need to increase the height of the tree.*/ \
node_t parent = M_F(name, _new_node)(); \
node_t parent = M_F(name, _new_node)(M_R_EXPAND_void); \
parent->num = 1; \
/* TBC: DO_INIT_MOVE instead ? If key was in a node !*/ \
M_CALL_INIT_SET(key_oplist, parent->key[0], *key_ptr); \
@@ -741,7 +742,7 @@
/* Non root node. Get the parent node */ \
node_t parent = pit->parent[--pit->num]; \
/* Insert into parent (It is big enough to receive temporary one more) */ \
i = M_F(name, _search_and_insert_in_node)(parent, leaf, *key_ptr); \
i = M_F(name, _i_search_and_insert_in_node)M_R(parent, leaf, *key_ptr); \
parent->kind.node[i] = leaf; \
parent->kind.node[i+1] = nleaf; \
/* Test if parent node is full? */ \
@@ -754,7 +755,7 @@
int nnp = N / 2; \
int np = N - nnp; \
M_ASSERT (nnp > 0 && np > 0 && nnp+np+1 == N+1); \
node_t nparent = M_F(name, _new_node)(); \
node_t nparent = M_F(name, _new_node)(M_R_EXPAND_void); \
/* Move half items to new node (Like a classic B-TREE) \
and the median key to the grand-parent*/ \
memmove(&nparent->key[0], &parent->key[np+1], sizeof(key_t)*(unsigned int)nnp); \
@@ -772,7 +773,7 @@
} \
} \
\
M_INLINE value_t *M_F(name, _safe_get)(tree_t b, key_t const key) \
M_P(value_t *, name, _safe_get, tree_t b, key_t const key) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
/* Not optimized implementation */ \
@@ -781,7 +782,7 @@
M_IF(isMap)( \
value_t v; \
M_CALL_INIT(value_oplist, v); \
M_F(name, _set_at)(b, key, v); \
M_F(name, _set_at)M_R(b, key, v); \
M_CALL_CLEAR(value_oplist, v); \
, \
M_F(name, _push)(b, key); \
@@ -791,10 +792,10 @@
return ret; \
} \
\
M_INLINE int \
M_F(name, _search_and_remove_in_leaf)(node_t n, key_t const key) \
M_P(int, name, _i_search_and_remove_in_leaf, node_t n, key_t const key) \
{ \
M_ASSERT(M_F(name, _is_leaf)(n)); \
M_ASSERT_POOL(); \
const int num = M_F(name, _get_num)(n); \
for(int i = 0; i < num; i++) { \
const int cmp = M_CALL_CMP(key_oplist, key, n->key[i]); \
@@ -811,10 +812,11 @@
return -1; /* Not found */ \
} \
\
M_INLINE void M_F(name, _left_shift)(node_t parent, int k) \
M_P(void, name, _i_left_shift, node_t parent, int k) \
{ \
M_ASSERT (parent != NULL && !M_F(name, _is_leaf)(parent)); \
M_ASSERT (0 <= k && k < M_F(name, _get_num)(parent)); \
M_ASSERT_POOL(); \
node_t left = parent->kind.node[k]; \
node_t right = parent->kind.node[k+1]; \
M_ASSERT (left != NULL && right != NULL); \
@@ -846,10 +848,11 @@
M_ASSERT (left->num != 0); \
} \
\
M_INLINE void M_F(name, _right_shift)(node_t parent, int k) \
M_P(void, name, _i_right_shift, node_t parent, int k) \
{ \
M_ASSERT (parent != NULL && !M_F(name, _is_leaf)(parent)); \
M_ASSERT (0 <= k && k < M_F(name, _get_num)(parent)); \
M_ASSERT_POOL(); \
node_t left = parent->kind.node[k]; \
node_t right = parent->kind.node[k+1]; \
M_ASSERT (left != NULL && right != NULL); \
@@ -880,7 +883,7 @@
M_ASSERT (left->num != 0); \
} \
\
M_INLINE void M_F(name, _merge_node)(node_t parent, int k, bool leaf) \
M_P(void, name, _i_merge_node, node_t parent, int k, bool leaf) \
{ \
M_ASSERT (parent != NULL && !M_F(name, _is_leaf)(parent)); \
M_ASSERT (0 <= k && k < M_F(name, _get_num(parent))); \
@@ -916,7 +919,7 @@
/* We can also cache the index when we descend the tree. \
TODO: Bench if this is worth the effort.*/ \
M_INLINE int \
M_F(name, _search_for_node)(node_t parent, node_t child) \
M_F(name, _i_search_for_node)(node_t parent, node_t child) \
{ \
M_ASSERT (!M_F(name, _is_leaf)(parent)); \
int i = 0; \
@@ -928,12 +931,12 @@
} \
} \
\
M_INLINE bool M_F(name, _erase)(tree_t b, key_t const key) \
M_P(bool, name, _erase, tree_t b, key_t const key) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
pit_t pit; \
node_t leaf = M_F(name, _search_for_leaf)(pit, b, key); \
int k = M_F(name, _search_and_remove_in_leaf)(leaf, key); \
node_t leaf = M_F(name, _i_search_for_leaf)(pit, b, key); \
int k = M_F(name, _i_search_and_remove_in_leaf)M_R(leaf, key); \
/* If key is not found ==> erase failed */ \
if (k < 0) return false; \
/* Remove one item from the B+TREE */ \
@@ -949,15 +952,15 @@
/* Search for node 'leaf' in parent */ \
node_t parent = pit->parent[--pit->num]; \
M_ASSERT (parent != NULL); \
k = M_F(name, _search_for_node)(parent, leaf); \
k = M_F(name, _i_search_for_node)(parent, leaf); \
/* Look for the neighbour of the removed key. */ \
/* if we can steal one key from them to keep our node balanced */ \
if (k > 0 && M_F(name, _get_num)(parent->kind.node[k-1]) > N/2) { \
M_F(name, _left_shift)(parent, k-1); \
M_F(name, _i_left_shift)M_R(parent, k-1); \
return true; \
} else if (k < M_F(name, _get_num)(parent) \
&& M_F(name, _get_num)(parent->kind.node[k+1]) > N/2) { \
M_F(name, _right_shift)(parent, k); \
M_F(name, _i_right_shift)M_R(parent, k); \
return true; \
} \
/* Merge both nodes, removing 'k' from parent */ \
@@ -965,7 +968,7 @@
k--; \
M_ASSERT(k >= 0 && k < M_F(name, _get_num)(parent)); \
/* Merge 'k' & 'k+1' & remove 'k' from parent */ \
M_F(name, _merge_node)(parent, k, pass1); \
M_F(name, _i_merge_node)M_R(parent, k, pass1); \
/* Check if we need to continue */ \
if (M_F(name, _get_num)(parent) >= N/2) \
return true; \
@@ -984,16 +987,17 @@
} \
} \
\
M_INLINE bool M_F(name, _pop_at)(value_t *ptr, tree_t b, key_t const key) \
M_P(bool, name, _pop_at, value_t *ptr, tree_t b, key_t const key) \
{ \
if (ptr != NULL) { \
/* Not optimized tree parsing */ \
value_t *ref = M_F(name, _get)(b, key); \
if (ref == NULL) { \
return false; \
} \
M_CALL_SET(value_oplist, *ptr, *ref); \
} \
return M_F(name, _erase)(b, key); \
return M_F(name, _erase)M_R(b, key); \
} \
\
M_INLINE value_t * \
@@ -1046,13 +1050,12 @@
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
} \
\
M_INLINE void \
M_F(name, _move)(tree_t b, tree_t ref) \
M_P(void, name, _move, tree_t b, tree_t ref) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, ref); \
M_ASSERT (b != ref); \
M_F(name,_clear)(b); \
M_F(name,_clear)M_R(b); \
M_F(name,_init_move)(b, ref); \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
} \
@@ -1161,7 +1164,7 @@
M_BPTR33_CONTRACT(N, isMulti, key_oplist, b); \
M_ASSERT (it != NULL); \
pit_t pit; \
node_t n = M_F(name, _search_for_leaf)(pit, b, key); \
node_t n = M_F(name, _i_search_for_leaf)(pit, b, key); \
it->node = n; \
int i; \
M_BPTR33_NODE_CONTRACT(N, isMulti, key_oplist, n, b->root); \
@@ -1259,29 +1262,29 @@
, /* NO HASH METHOD */ ) \
\
M_IF_METHOD_BOTH(GET_STR, key_oplist, value_oplist)( \
M_INLINE void M_F(name, _get_str)(m_string_t str, \
const tree_t t1, bool append) { \
M_P(void, name, _get_str, m_string_t str, const tree_t t1, bool append) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, t1); \
M_ASSERT(str != NULL); \
(append ? m_string_cat_cstr : m_string_set_cstr) (str, "["); \
(append ? m_string_cat_cstr : m_string_set_cstr) M_R(str, "["); \
bool commaToPrint = false; \
it_t it; \
for (M_F(name, _it)(it, t1) ; \
!M_F(name, _end_p)(it); \
M_F(name, _next)(it)) { \
if (commaToPrint) \
m_string_push_back (str, M_GET_SEPARATOR key_oplist); \
m_string_push_back M_R(str, M_GET_SEPARATOR key_oplist); \
commaToPrint = true; \
subtype_t const *ref1 = M_F(name, _cref)(it); \
M_IF(isMap)( \
M_CALL_GET_STR(key_oplist, str, *ref1->key_ptr, true); \
m_string_cat_cstr(str, ":"); \
m_string_cat_cstr M_R(str, ":"); \
M_CALL_GET_STR(value_oplist,str, *ref1->value_ptr, true) \
, \
M_CALL_GET_STR(key_oplist, str, *ref1, true); \
); \
} \
m_string_push_back (str, ']'); \
m_string_push_back M_R(str, ']'); \
} \
, /* NO GET_STR */ ) \
\
@@ -1314,12 +1317,11 @@
, /* no out_str */ ) \
\
M_IF_METHOD_BOTH(PARSE_STR, key_oplist, value_oplist)( \
M_INLINE bool \
M_F(name, _parse_str)(tree_t t1, const char str[], const char **endp) \
M_P(bool, name, _parse_str, tree_t t1, const char str[], const char **endp) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, t1); \
M_ASSERT (str != NULL); \
M_F(name,_reset)(t1); \
M_F(name,_reset)M_R(t1); \
int c = *str++; \
if (M_UNLIKELY (c != '[')) { c = 0; goto exit; } \
c = *str++; \
@@ -1335,9 +1337,9 @@
b = M_CALL_PARSE_STR(value_oplist, value, str, &str); \
c = m_core_str_nospace(&str); \
if (b == false || c == 0) { c = 0; break; } \
M_F(name, _set_at)(t1, key, value); \
M_F(name, _set_at)M_R(t1, key, value); \
, \
M_F(name, _push)(t1, key); \
M_F(name, _push)M_R(t1, key); \
) \
} while (c == M_GET_SEPARATOR key_oplist); \
} \
@@ -1349,12 +1351,11 @@
, /* no parse_str */ ) \
\
M_IF_METHOD_BOTH(IN_STR, key_oplist, value_oplist)( \
M_INLINE bool \
M_F(name, _in_str)(tree_t t1, FILE *file) \
M_P(bool, name, _in_str, tree_t t1, FILE *file) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, t1); \
M_ASSERT (file != NULL); \
M_F(name,_reset)(t1); \
M_F(name,_reset)M_R(t1); \
int c = fgetc(file); \
if (M_UNLIKELY (c != '[')) return false; \
c = fgetc(file); \
@@ -1371,9 +1372,9 @@
b = M_CALL_IN_STR(value_oplist,value, file); \
c = m_core_fgetc_nospace(file); \
if (b == false || c == EOF) { c = 0; break; } \
M_F(name, _set_at)(t1, key, value) \
M_F(name, _set_at)M_R(t1, key, value) \
, \
M_F(name, _push)(t1, key) \
M_F(name, _push)M_R(t1, key) \
); \
} while (c == M_GET_SEPARATOR key_oplist); \
} \
@@ -1427,15 +1428,14 @@
, /* no OUT_SERIAL */ ) \
\
M_IF_METHOD_BOTH(IN_SERIAL, key_oplist, value_oplist)( \
M_INLINE m_serial_return_code_t \
M_F(name, _in_serial)(tree_t t1, m_serial_read_t f) \
M_P(m_serial_return_code_t, name, _in_serial, tree_t t1, m_serial_read_t f) \
{ \
M_BPTR33_CONTRACT(N, isMulti, key_oplist, t1); \
M_ASSERT (f != NULL && f->m_interface != NULL); \
m_serial_local_t local; \
m_serial_return_code_t ret; \
size_t estimated_size = 0; \
M_F(name,_reset)(t1); \
M_F(name,_reset)M_R(t1); \
M_QLET(1, key, key_t, key_oplist) \
M_IF(isMap)(M_QLET(2, value, value_t, value_oplist) , /*nothing*/) { \
M_IF(isMap)( \
@@ -1448,7 +1448,7 @@
if (ret != M_SERIAL_OK_CONTINUE) break; \
ret = M_CALL_IN_SERIAL(value_oplist, value, f); \
if (ret != M_SERIAL_OK_DONE) break; \
M_F(name, _set_at)(t1, key, value); \
M_F(name, _set_at)M_R(t1, key, value); \
} while ((ret = f->m_interface->read_map_next(local, f)) == M_SERIAL_OK_CONTINUE); \
, /* queue style */ \
ret = f->m_interface->read_array_start(local, f, &estimated_size); \
@@ -1456,7 +1456,7 @@
do { \
ret = M_CALL_IN_SERIAL(key_oplist, key, f); \
if (ret != M_SERIAL_OK_DONE) break; \
M_F(name, _push)(t1, key); \
M_F(name, _push)M_R(t1, key); \
} while ((ret = f->m_interface->read_array_next(local, f)) == M_SERIAL_OK_CONTINUE); \
) /* End of IF isMap */ \
} \
+14 -10
View File
@@ -412,33 +412,37 @@ M_BEGIN_PROTECTED_CODE
#endif
// FIXME: Rename to M_USE_MEMORY_CONTEXT?
#ifndef M_USE_POOL
// Memory pool parameter not used for memory function. Stub it with 0 as it won't be used (global definition).
# define M_P_EXPAND
# define M_P_EXPAND_void void
# define M_R_EXPAND
# define M_R_EXPAND_void
# define M_R
# define M_ASSERT_POOL()
#else
// Memory pool parameter is used for function using memory. (local definition to the function).
// Expand to a prototype to a function that may not trigger exception
# define M_P_EXPAND M_USE_POOL m_pool,
# define M_P_EXPAND_void M_USE_POOL m_pool
# define M_R_EXPAND m_pool ,
# define M_R_EXPAND_void m_pool
# define M_R(...) (m_pool, __VA_ARGS__)
# define M_ASSERT_POOL() (void) m_pool
#endif
// Expand to a prototype of a function that may trigger exception / accept memory context
// TODO: If not M_F, M_INLINE overloaded and no M_P_EXPAND, simplify M_P
// (faster build: 0.54s instead of 0.80s in extreme expanding
// M_P: Expand to a prototype of a function that may trigger exception / accept memory context
// M_N: Expand to a prototype of a function that may not trigger exception or accept memory context
// If not M_F, no overloaded M_INLINE and no M_P_EXPAND, simplify M_P
// (faster build: 0.54s instead of 0.80s in extreme expanding)
#if !defined(M_USE_POOL) && !defined(M_F) && !defined(M_USE_FINE_GRAINED_LINKAGE) && !defined(M_USE_DEF) && !defined(M_USE_DECL)
# define M_P(rettype, name, suffix, ...) static inline rettype name ## suffix(__VA_ARGS__)
# define M_N(rettype, name, suffix, ...) static inline rettype name ## suffix(__VA_ARGS__)
#else
# define M_P(rettype, name, suffix, ...) M_INLINE rettype M_F(name, suffix)(M_P_EXPAND __VA_ARGS__)
//#define M_P(rettype, name, suffix, ...) static inline rettype name ## suffix(__VA_ARGS__)
// Expand to a prototype of a function that may not trigger exception or accept memory context
# define M_N(rettype, name, suffix, ...) M_INLINE rettype M_F(name, suffix)(__VA_ARGS__)
// TODO: Add for API special, the support of the POOL special keyword ==> m_pool
// Or document m_pool directly? Can still be overriden later.
#endif
// TODO: Add for all Assertions: M_ASSERT_POOL()
// to avoid warnings about unused parameter m_pool.