mirror of
git://gcc.gnu.org/git/gcc.git
synced 2024-12-01 13:09:55 +08:00
955 lines
29 KiB
C
955 lines
29 KiB
C
/* Lock-free btree for manually registered unwind frames. */
|
|
/* Copyright (C) 2022-2024 Free Software Foundation, Inc.
|
|
Contributed by Thomas Neumann
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 3, or (at your option) any later
|
|
version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
Under Section 7 of GPL version 3, you are granted additional
|
|
permissions described in the GCC Runtime Library Exception, version
|
|
3.1, as published by the Free Software Foundation.
|
|
|
|
You should have received a copy of the GNU General Public License and
|
|
a copy of the GCC Runtime Library Exception along with this program;
|
|
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#ifndef GCC_UNWIND_DW2_BTREE_H
|
|
#define GCC_UNWIND_DW2_BTREE_H
|
|
|
|
#include <stdbool.h>
|
|
|
|
// Common logic for version locks.
|
|
struct version_lock
|
|
{
|
|
// The lock itself. The lowest bit indicates an exclusive lock,
|
|
// the second bit indicates waiting threads. All other bits are
|
|
// used as counter to recognize changes.
|
|
// Overflows are okay here, we must only prevent overflow to the
|
|
// same value within one lock_optimistic/validate
|
|
// range. Even on 32 bit platforms that would require 1 billion
|
|
// frame registrations within the time span of a few assembler
|
|
// instructions.
|
|
uintptr_type version_lock;
|
|
};
|
|
|
|
#ifdef __GTHREAD_HAS_COND
|
|
// We should never get contention within the tree as it rarely changes.
|
|
// But if we ever do get contention we use these for waiting.
|
|
static __gthread_mutex_t version_lock_mutex = __GTHREAD_MUTEX_INIT;
|
|
static __gthread_cond_t version_lock_cond = __GTHREAD_COND_INIT;
|
|
#endif
|
|
|
|
// Initialize in locked state.
|
|
static inline void
|
|
version_lock_initialize_locked_exclusive (struct version_lock *vl)
|
|
{
|
|
vl->version_lock = 1;
|
|
}
|
|
|
|
// Try to lock the node exclusive.
|
|
static inline bool
|
|
version_lock_try_lock_exclusive (struct version_lock *vl)
|
|
{
|
|
uintptr_type state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
if (state & 1)
|
|
return false;
|
|
return __atomic_compare_exchange_n (&(vl->version_lock), &state, state | 1,
|
|
false, __ATOMIC_SEQ_CST,
|
|
__ATOMIC_SEQ_CST);
|
|
}
|
|
|
|
// Lock the node exclusive, blocking as needed.
|
|
static void
|
|
version_lock_lock_exclusive (struct version_lock *vl)
|
|
{
|
|
#ifndef __GTHREAD_HAS_COND
|
|
restart:
|
|
#endif
|
|
|
|
// We should virtually never get contention here, as frame
|
|
// changes are rare.
|
|
uintptr_type state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
if (!(state & 1))
|
|
{
|
|
if (__atomic_compare_exchange_n (&(vl->version_lock), &state, state | 1,
|
|
false, __ATOMIC_SEQ_CST,
|
|
__ATOMIC_SEQ_CST))
|
|
return;
|
|
}
|
|
|
|
// We did get contention, wait properly.
|
|
#ifdef __GTHREAD_HAS_COND
|
|
__gthread_mutex_lock (&version_lock_mutex);
|
|
state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
while (true)
|
|
{
|
|
// Check if the lock is still held.
|
|
if (!(state & 1))
|
|
{
|
|
if (__atomic_compare_exchange_n (&(vl->version_lock), &state,
|
|
state | 1, false, __ATOMIC_SEQ_CST,
|
|
__ATOMIC_SEQ_CST))
|
|
{
|
|
__gthread_mutex_unlock (&version_lock_mutex);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Register waiting thread.
|
|
if (!(state & 2))
|
|
{
|
|
if (!__atomic_compare_exchange_n (&(vl->version_lock), &state,
|
|
state | 2, false, __ATOMIC_SEQ_CST,
|
|
__ATOMIC_SEQ_CST))
|
|
continue;
|
|
}
|
|
|
|
// And sleep.
|
|
__gthread_cond_wait (&version_lock_cond, &version_lock_mutex);
|
|
state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
}
|
|
#else
|
|
// Spin if we do not have condition variables available.
|
|
// We expect no contention here, spinning should be okay.
|
|
goto restart;
|
|
#endif
|
|
}
|
|
|
|
// Release a locked node and increase the version lock.
|
|
static void
|
|
version_lock_unlock_exclusive (struct version_lock *vl)
|
|
{
|
|
// increase version, reset exclusive lock bits
|
|
uintptr_type state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
uintptr_type ns = (state + 4) & (~((uintptr_type) 3));
|
|
state = __atomic_exchange_n (&(vl->version_lock), ns, __ATOMIC_SEQ_CST);
|
|
|
|
#ifdef __GTHREAD_HAS_COND
|
|
if (state & 2)
|
|
{
|
|
// Wake up waiting threads. This should be extremely rare.
|
|
__gthread_mutex_lock (&version_lock_mutex);
|
|
__gthread_cond_broadcast (&version_lock_cond);
|
|
__gthread_mutex_unlock (&version_lock_mutex);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Acquire an optimistic "lock". Note that this does not lock at all, it
|
|
// only allows for validation later.
|
|
static inline bool
|
|
version_lock_lock_optimistic (const struct version_lock *vl, uintptr_type *lock)
|
|
{
|
|
uintptr_type state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
*lock = state;
|
|
|
|
// Acquiring the lock fails when there is currently an exclusive lock.
|
|
return !(state & 1);
|
|
}
|
|
|
|
// Validate a previously acquired "lock".
|
|
static inline bool
|
|
version_lock_validate (const struct version_lock *vl, uintptr_type lock)
|
|
{
|
|
// Prevent the reordering of non-atomic loads behind the atomic load.
|
|
// Hans Boehm, Can Seqlocks Get Along with Programming Language Memory
|
|
// Models?, Section 4.
|
|
__atomic_thread_fence (__ATOMIC_ACQUIRE);
|
|
|
|
// Check that the node is still in the same state.
|
|
uintptr_type state = __atomic_load_n (&(vl->version_lock), __ATOMIC_SEQ_CST);
|
|
return (state == lock);
|
|
}
|
|
|
|
// The largest possible separator value.
|
|
static const uintptr_type max_separator = ~((uintptr_type) (0));
|
|
|
|
struct btree_node;
|
|
|
|
// Inner entry. The child tree contains all entries <= separator.
|
|
struct inner_entry
|
|
{
|
|
uintptr_type separator;
|
|
struct btree_node *child;
|
|
};
|
|
|
|
// Leaf entry. Stores an object entry.
|
|
struct leaf_entry
|
|
{
|
|
uintptr_type base, size;
|
|
struct object *ob;
|
|
};
|
|
|
|
// Node types.
|
|
enum node_type
|
|
{
|
|
btree_node_inner,
|
|
btree_node_leaf,
|
|
btree_node_free
|
|
};
|
|
|
|
// Node sizes. Chosen such that the result size is roughly 256 bytes.
|
|
#define max_fanout_inner 15
|
|
#define max_fanout_leaf 10
|
|
|
|
// A btree node.
|
|
struct btree_node
|
|
{
|
|
// The version lock used for optimistic lock coupling.
|
|
struct version_lock version_lock;
|
|
// The number of entries.
|
|
unsigned entry_count;
|
|
// The type.
|
|
enum node_type type;
|
|
// The payload.
|
|
union
|
|
{
|
|
// The inner nodes have fence keys, i.e., the right-most entry includes a
|
|
// separator.
|
|
struct inner_entry children[max_fanout_inner];
|
|
struct leaf_entry entries[max_fanout_leaf];
|
|
} content;
|
|
};
|
|
|
|
// Is an inner node?
|
|
static inline bool
|
|
btree_node_is_inner (const struct btree_node *n)
|
|
{
|
|
return n->type == btree_node_inner;
|
|
}
|
|
|
|
// Is a leaf node?
|
|
static inline bool
|
|
btree_node_is_leaf (const struct btree_node *n)
|
|
{
|
|
return n->type == btree_node_leaf;
|
|
}
|
|
|
|
// Should the node be merged?
|
|
static inline bool
|
|
btree_node_needs_merge (const struct btree_node *n)
|
|
{
|
|
return n->entry_count < (btree_node_is_inner (n) ? (max_fanout_inner / 2)
|
|
: (max_fanout_leaf / 2));
|
|
}
|
|
|
|
// Get the fence key for inner nodes.
|
|
static inline uintptr_type
|
|
btree_node_get_fence_key (const struct btree_node *n)
|
|
{
|
|
// For inner nodes we just return our right-most entry.
|
|
return n->content.children[n->entry_count - 1].separator;
|
|
}
|
|
|
|
// Find the position for a slot in an inner node.
|
|
static unsigned
|
|
btree_node_find_inner_slot (const struct btree_node *n, uintptr_type value)
|
|
{
|
|
for (unsigned index = 0, ec = n->entry_count; index != ec; ++index)
|
|
if (n->content.children[index].separator >= value)
|
|
return index;
|
|
return n->entry_count;
|
|
}
|
|
|
|
// Find the position for a slot in a leaf node.
|
|
static unsigned
|
|
btree_node_find_leaf_slot (const struct btree_node *n, uintptr_type value)
|
|
{
|
|
for (unsigned index = 0, ec = n->entry_count; index != ec; ++index)
|
|
if (n->content.entries[index].base + n->content.entries[index].size > value)
|
|
return index;
|
|
return n->entry_count;
|
|
}
|
|
|
|
// Try to lock the node exclusive.
|
|
static inline bool
|
|
btree_node_try_lock_exclusive (struct btree_node *n)
|
|
{
|
|
return version_lock_try_lock_exclusive (&(n->version_lock));
|
|
}
|
|
|
|
// Lock the node exclusive, blocking as needed.
|
|
static inline void
|
|
btree_node_lock_exclusive (struct btree_node *n)
|
|
{
|
|
version_lock_lock_exclusive (&(n->version_lock));
|
|
}
|
|
|
|
// Release a locked node and increase the version lock.
|
|
static inline void
|
|
btree_node_unlock_exclusive (struct btree_node *n)
|
|
{
|
|
version_lock_unlock_exclusive (&(n->version_lock));
|
|
}
|
|
|
|
// Acquire an optimistic "lock". Note that this does not lock at all, it
|
|
// only allows for validation later.
|
|
static inline bool
|
|
btree_node_lock_optimistic (const struct btree_node *n, uintptr_type *lock)
|
|
{
|
|
return version_lock_lock_optimistic (&(n->version_lock), lock);
|
|
}
|
|
|
|
// Validate a previously acquire lock.
|
|
static inline bool
|
|
btree_node_validate (const struct btree_node *n, uintptr_type lock)
|
|
{
|
|
return version_lock_validate (&(n->version_lock), lock);
|
|
}
|
|
|
|
// Insert a new separator after splitting.
|
|
static void
|
|
btree_node_update_separator_after_split (struct btree_node *n,
|
|
uintptr_type old_separator,
|
|
uintptr_type new_separator,
|
|
struct btree_node *new_right)
|
|
{
|
|
unsigned slot = btree_node_find_inner_slot (n, old_separator);
|
|
for (unsigned index = n->entry_count; index > slot; --index)
|
|
n->content.children[index] = n->content.children[index - 1];
|
|
n->content.children[slot].separator = new_separator;
|
|
n->content.children[slot + 1].child = new_right;
|
|
n->entry_count++;
|
|
}
|
|
|
|
// A btree. Suitable for static initialization, all members are zero at the
|
|
// beginning.
|
|
struct btree
|
|
{
|
|
// The root of the btree.
|
|
struct btree_node *root;
|
|
// The free list of released node.
|
|
struct btree_node *free_list;
|
|
// The version lock used to protect the root.
|
|
struct version_lock root_lock;
|
|
};
|
|
|
|
// Initialize a btree. Not actually used, just for exposition.
|
|
static inline void
|
|
btree_init (struct btree *t)
|
|
{
|
|
t->root = NULL;
|
|
t->free_list = NULL;
|
|
t->root_lock.version_lock = 0;
|
|
};
|
|
|
|
static void
|
|
btree_release_tree_recursively (struct btree *t, struct btree_node *n);
|
|
|
|
// Destroy a tree and release all nodes.
|
|
static void
|
|
btree_destroy (struct btree *t)
|
|
{
|
|
// Disable the mechanism before cleaning up.
|
|
struct btree_node *old_root
|
|
= __atomic_exchange_n (&(t->root), NULL, __ATOMIC_SEQ_CST);
|
|
if (old_root)
|
|
btree_release_tree_recursively (t, old_root);
|
|
|
|
// Release all free nodes.
|
|
while (t->free_list)
|
|
{
|
|
struct btree_node *next = t->free_list->content.children[0].child;
|
|
free (t->free_list);
|
|
t->free_list = next;
|
|
}
|
|
}
|
|
|
|
// Allocate a node. This node will be returned in locked exclusive state.
|
|
static struct btree_node *
|
|
btree_allocate_node (struct btree *t, bool inner)
|
|
{
|
|
while (true)
|
|
{
|
|
// Try the free list first.
|
|
struct btree_node *next_free
|
|
= __atomic_load_n (&(t->free_list), __ATOMIC_SEQ_CST);
|
|
if (next_free)
|
|
{
|
|
if (!btree_node_try_lock_exclusive (next_free))
|
|
continue;
|
|
// The node might no longer be free, check that again after acquiring
|
|
// the exclusive lock.
|
|
if (next_free->type == btree_node_free)
|
|
{
|
|
struct btree_node *ex = next_free;
|
|
if (__atomic_compare_exchange_n (
|
|
&(t->free_list), &ex, next_free->content.children[0].child,
|
|
false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
|
|
{
|
|
next_free->entry_count = 0;
|
|
next_free->type = inner ? btree_node_inner : btree_node_leaf;
|
|
return next_free;
|
|
}
|
|
}
|
|
btree_node_unlock_exclusive (next_free);
|
|
continue;
|
|
}
|
|
|
|
// No free node available, allocate a new one.
|
|
struct btree_node *new_node
|
|
= (struct btree_node *) (malloc (sizeof (struct btree_node)));
|
|
version_lock_initialize_locked_exclusive (
|
|
&(new_node->version_lock)); // initialize the node in locked state.
|
|
new_node->entry_count = 0;
|
|
new_node->type = inner ? btree_node_inner : btree_node_leaf;
|
|
return new_node;
|
|
}
|
|
}
|
|
|
|
// Release a node. This node must be currently locked exclusively and will
|
|
// be placed in the free list.
|
|
static void
|
|
btree_release_node (struct btree *t, struct btree_node *node)
|
|
{
|
|
// We cannot release the memory immediately because there might still be
|
|
// concurrent readers on that node. Put it in the free list instead.
|
|
node->type = btree_node_free;
|
|
struct btree_node *next_free
|
|
= __atomic_load_n (&(t->free_list), __ATOMIC_SEQ_CST);
|
|
do
|
|
{
|
|
node->content.children[0].child = next_free;
|
|
} while (!__atomic_compare_exchange_n (&(t->free_list), &next_free, node,
|
|
false, __ATOMIC_SEQ_CST,
|
|
__ATOMIC_SEQ_CST));
|
|
btree_node_unlock_exclusive (node);
|
|
}
|
|
|
|
// Recursively release a tree. The btree is by design very shallow, thus
|
|
// we can risk recursion here.
|
|
static void
|
|
btree_release_tree_recursively (struct btree *t, struct btree_node *node)
|
|
{
|
|
btree_node_lock_exclusive (node);
|
|
if (btree_node_is_inner (node))
|
|
{
|
|
for (unsigned index = 0; index < node->entry_count; ++index)
|
|
btree_release_tree_recursively (t, node->content.children[index].child);
|
|
}
|
|
btree_release_node (t, node);
|
|
}
|
|
|
|
// Check if we are splitting the root.
|
|
static void
|
|
btree_handle_root_split (struct btree *t, struct btree_node **node,
|
|
struct btree_node **parent)
|
|
{
|
|
// We want to keep the root pointer stable to allow for contention
|
|
// free reads. Thus, we split the root by first moving the content
|
|
// of the root node to a new node, and then split that new node.
|
|
if (!*parent)
|
|
{
|
|
// Allocate a new node, this guarantees us that we will have a parent
|
|
// afterwards.
|
|
struct btree_node *new_node
|
|
= btree_allocate_node (t, btree_node_is_inner (*node));
|
|
struct btree_node *old_node = *node;
|
|
new_node->entry_count = old_node->entry_count;
|
|
new_node->content = old_node->content;
|
|
old_node->content.children[0].separator = max_separator;
|
|
old_node->content.children[0].child = new_node;
|
|
old_node->entry_count = 1;
|
|
old_node->type = btree_node_inner;
|
|
|
|
*parent = old_node;
|
|
*node = new_node;
|
|
}
|
|
}
|
|
|
|
// Split an inner node.
|
|
static void
|
|
btree_split_inner (struct btree *t, struct btree_node **inner,
|
|
struct btree_node **parent, uintptr_type target)
|
|
{
|
|
// Check for the root.
|
|
btree_handle_root_split (t, inner, parent);
|
|
|
|
// Create two inner node.
|
|
uintptr_type right_fence = btree_node_get_fence_key (*inner);
|
|
struct btree_node *left_inner = *inner;
|
|
struct btree_node *right_inner = btree_allocate_node (t, true);
|
|
unsigned split = left_inner->entry_count / 2;
|
|
right_inner->entry_count = left_inner->entry_count - split;
|
|
for (unsigned index = 0; index < right_inner->entry_count; ++index)
|
|
right_inner->content.children[index]
|
|
= left_inner->content.children[split + index];
|
|
left_inner->entry_count = split;
|
|
uintptr_type left_fence = btree_node_get_fence_key (left_inner);
|
|
btree_node_update_separator_after_split (*parent, right_fence, left_fence,
|
|
right_inner);
|
|
if (target <= left_fence)
|
|
{
|
|
*inner = left_inner;
|
|
btree_node_unlock_exclusive (right_inner);
|
|
}
|
|
else
|
|
{
|
|
*inner = right_inner;
|
|
btree_node_unlock_exclusive (left_inner);
|
|
}
|
|
}
|
|
|
|
// Split a leaf node.
|
|
static void
|
|
btree_split_leaf (struct btree *t, struct btree_node **leaf,
|
|
struct btree_node **parent, uintptr_type fence,
|
|
uintptr_type target)
|
|
{
|
|
// Check for the root.
|
|
btree_handle_root_split (t, leaf, parent);
|
|
|
|
// Create two leaf nodes.
|
|
uintptr_type right_fence = fence;
|
|
struct btree_node *left_leaf = *leaf;
|
|
struct btree_node *right_leaf = btree_allocate_node (t, false);
|
|
unsigned split = left_leaf->entry_count / 2;
|
|
right_leaf->entry_count = left_leaf->entry_count - split;
|
|
for (unsigned index = 0; index != right_leaf->entry_count; ++index)
|
|
right_leaf->content.entries[index]
|
|
= left_leaf->content.entries[split + index];
|
|
left_leaf->entry_count = split;
|
|
uintptr_type left_fence = right_leaf->content.entries[0].base - 1;
|
|
btree_node_update_separator_after_split (*parent, right_fence, left_fence,
|
|
right_leaf);
|
|
if (target <= left_fence)
|
|
{
|
|
*leaf = left_leaf;
|
|
btree_node_unlock_exclusive (right_leaf);
|
|
}
|
|
else
|
|
{
|
|
*leaf = right_leaf;
|
|
btree_node_unlock_exclusive (left_leaf);
|
|
}
|
|
}
|
|
|
|
// Merge (or balance) child nodes.
|
|
static struct btree_node *
|
|
btree_merge_node (struct btree *t, unsigned child_slot,
|
|
struct btree_node *parent, uintptr_type target)
|
|
{
|
|
// Choose the emptiest neighbor and lock both. The target child is already
|
|
// locked.
|
|
unsigned left_slot;
|
|
struct btree_node *left_node, *right_node;
|
|
if ((child_slot == 0)
|
|
|| (((child_slot + 1) < parent->entry_count)
|
|
&& (parent->content.children[child_slot + 1].child->entry_count
|
|
< parent->content.children[child_slot - 1].child->entry_count)))
|
|
{
|
|
left_slot = child_slot;
|
|
left_node = parent->content.children[left_slot].child;
|
|
right_node = parent->content.children[left_slot + 1].child;
|
|
btree_node_lock_exclusive (right_node);
|
|
}
|
|
else
|
|
{
|
|
left_slot = child_slot - 1;
|
|
left_node = parent->content.children[left_slot].child;
|
|
right_node = parent->content.children[left_slot + 1].child;
|
|
btree_node_lock_exclusive (left_node);
|
|
}
|
|
|
|
// Can we merge both nodes into one node?
|
|
unsigned total_count = left_node->entry_count + right_node->entry_count;
|
|
unsigned max_count
|
|
= btree_node_is_inner (left_node) ? max_fanout_inner : max_fanout_leaf;
|
|
if (total_count <= max_count)
|
|
{
|
|
// Merge into the parent?
|
|
if (parent->entry_count == 2)
|
|
{
|
|
// Merge children into parent. This can only happen at the root.
|
|
if (btree_node_is_inner (left_node))
|
|
{
|
|
for (unsigned index = 0; index != left_node->entry_count; ++index)
|
|
parent->content.children[index]
|
|
= left_node->content.children[index];
|
|
for (unsigned index = 0; index != right_node->entry_count;
|
|
++index)
|
|
parent->content.children[index + left_node->entry_count]
|
|
= right_node->content.children[index];
|
|
}
|
|
else
|
|
{
|
|
parent->type = btree_node_leaf;
|
|
for (unsigned index = 0; index != left_node->entry_count; ++index)
|
|
parent->content.entries[index]
|
|
= left_node->content.entries[index];
|
|
for (unsigned index = 0; index != right_node->entry_count;
|
|
++index)
|
|
parent->content.entries[index + left_node->entry_count]
|
|
= right_node->content.entries[index];
|
|
}
|
|
parent->entry_count = total_count;
|
|
btree_release_node (t, left_node);
|
|
btree_release_node (t, right_node);
|
|
return parent;
|
|
}
|
|
else
|
|
{
|
|
// Regular merge.
|
|
if (btree_node_is_inner (left_node))
|
|
{
|
|
for (unsigned index = 0; index != right_node->entry_count;
|
|
++index)
|
|
left_node->content.children[left_node->entry_count++]
|
|
= right_node->content.children[index];
|
|
}
|
|
else
|
|
{
|
|
for (unsigned index = 0; index != right_node->entry_count;
|
|
++index)
|
|
left_node->content.entries[left_node->entry_count++]
|
|
= right_node->content.entries[index];
|
|
}
|
|
parent->content.children[left_slot].separator
|
|
= parent->content.children[left_slot + 1].separator;
|
|
for (unsigned index = left_slot + 1; index + 1 < parent->entry_count;
|
|
++index)
|
|
parent->content.children[index]
|
|
= parent->content.children[index + 1];
|
|
parent->entry_count--;
|
|
btree_release_node (t, right_node);
|
|
btree_node_unlock_exclusive (parent);
|
|
return left_node;
|
|
}
|
|
}
|
|
|
|
// No merge possible, rebalance instead.
|
|
if (left_node->entry_count > right_node->entry_count)
|
|
{
|
|
// Shift from left to right.
|
|
unsigned to_shift
|
|
= (left_node->entry_count - right_node->entry_count) / 2;
|
|
if (btree_node_is_inner (left_node))
|
|
{
|
|
for (unsigned index = 0; index != right_node->entry_count; ++index)
|
|
{
|
|
unsigned pos = right_node->entry_count - 1 - index;
|
|
right_node->content.children[pos + to_shift]
|
|
= right_node->content.children[pos];
|
|
}
|
|
for (unsigned index = 0; index != to_shift; ++index)
|
|
right_node->content.children[index]
|
|
= left_node->content
|
|
.children[left_node->entry_count - to_shift + index];
|
|
}
|
|
else
|
|
{
|
|
for (unsigned index = 0; index != right_node->entry_count; ++index)
|
|
{
|
|
unsigned pos = right_node->entry_count - 1 - index;
|
|
right_node->content.entries[pos + to_shift]
|
|
= right_node->content.entries[pos];
|
|
}
|
|
for (unsigned index = 0; index != to_shift; ++index)
|
|
right_node->content.entries[index]
|
|
= left_node->content
|
|
.entries[left_node->entry_count - to_shift + index];
|
|
}
|
|
left_node->entry_count -= to_shift;
|
|
right_node->entry_count += to_shift;
|
|
}
|
|
else
|
|
{
|
|
// Shift from right to left.
|
|
unsigned to_shift
|
|
= (right_node->entry_count - left_node->entry_count) / 2;
|
|
if (btree_node_is_inner (left_node))
|
|
{
|
|
for (unsigned index = 0; index != to_shift; ++index)
|
|
left_node->content.children[left_node->entry_count + index]
|
|
= right_node->content.children[index];
|
|
for (unsigned index = 0; index != right_node->entry_count - to_shift;
|
|
++index)
|
|
right_node->content.children[index]
|
|
= right_node->content.children[index + to_shift];
|
|
}
|
|
else
|
|
{
|
|
for (unsigned index = 0; index != to_shift; ++index)
|
|
left_node->content.entries[left_node->entry_count + index]
|
|
= right_node->content.entries[index];
|
|
for (unsigned index = 0; index != right_node->entry_count - to_shift;
|
|
++index)
|
|
right_node->content.entries[index]
|
|
= right_node->content.entries[index + to_shift];
|
|
}
|
|
left_node->entry_count += to_shift;
|
|
right_node->entry_count -= to_shift;
|
|
}
|
|
uintptr_type left_fence;
|
|
if (btree_node_is_leaf (left_node))
|
|
{
|
|
left_fence = right_node->content.entries[0].base - 1;
|
|
}
|
|
else
|
|
{
|
|
left_fence = btree_node_get_fence_key (left_node);
|
|
}
|
|
parent->content.children[left_slot].separator = left_fence;
|
|
btree_node_unlock_exclusive (parent);
|
|
if (target <= left_fence)
|
|
{
|
|
btree_node_unlock_exclusive (right_node);
|
|
return left_node;
|
|
}
|
|
else
|
|
{
|
|
btree_node_unlock_exclusive (left_node);
|
|
return right_node;
|
|
}
|
|
}
|
|
|
|
// Insert an entry.
|
|
static bool
|
|
btree_insert (struct btree *t, uintptr_type base, uintptr_type size,
|
|
struct object *ob)
|
|
{
|
|
// Sanity check.
|
|
if (!size)
|
|
return false;
|
|
|
|
// Access the root.
|
|
struct btree_node *iter, *parent = NULL;
|
|
{
|
|
version_lock_lock_exclusive (&(t->root_lock));
|
|
iter = t->root;
|
|
if (iter)
|
|
{
|
|
btree_node_lock_exclusive (iter);
|
|
}
|
|
else
|
|
{
|
|
t->root = iter = btree_allocate_node (t, false);
|
|
}
|
|
version_lock_unlock_exclusive (&(t->root_lock));
|
|
}
|
|
|
|
// Walk down the btree with classic lock coupling and eager splits.
|
|
// Strictly speaking this is not performance optimal, we could use
|
|
// optimistic lock coupling until we hit a node that has to be modified.
|
|
// But that is more difficult to implement and frame registration is
|
|
// rare anyway, we use simple locking for now.
|
|
|
|
uintptr_type fence = max_separator;
|
|
while (btree_node_is_inner (iter))
|
|
{
|
|
// Use eager splits to avoid lock coupling up.
|
|
if (iter->entry_count == max_fanout_inner)
|
|
btree_split_inner (t, &iter, &parent, base);
|
|
|
|
unsigned slot = btree_node_find_inner_slot (iter, base);
|
|
if (parent)
|
|
btree_node_unlock_exclusive (parent);
|
|
parent = iter;
|
|
fence = iter->content.children[slot].separator;
|
|
iter = iter->content.children[slot].child;
|
|
btree_node_lock_exclusive (iter);
|
|
}
|
|
|
|
// Make sure we have space.
|
|
if (iter->entry_count == max_fanout_leaf)
|
|
btree_split_leaf (t, &iter, &parent, fence, base);
|
|
if (parent)
|
|
btree_node_unlock_exclusive (parent);
|
|
|
|
// Insert in node.
|
|
unsigned slot = btree_node_find_leaf_slot (iter, base);
|
|
if ((slot < iter->entry_count) && (iter->content.entries[slot].base == base))
|
|
{
|
|
// Duplicate entry, this should never happen.
|
|
btree_node_unlock_exclusive (iter);
|
|
return false;
|
|
}
|
|
for (unsigned index = iter->entry_count; index > slot; --index)
|
|
iter->content.entries[index] = iter->content.entries[index - 1];
|
|
struct leaf_entry *e = &(iter->content.entries[slot]);
|
|
e->base = base;
|
|
e->size = size;
|
|
e->ob = ob;
|
|
iter->entry_count++;
|
|
btree_node_unlock_exclusive (iter);
|
|
return true;
|
|
}
|
|
|
|
// Remove an entry.
|
|
static struct object *
|
|
btree_remove (struct btree *t, uintptr_type base)
|
|
{
|
|
// Access the root.
|
|
version_lock_lock_exclusive (&(t->root_lock));
|
|
struct btree_node *iter = t->root;
|
|
if (iter)
|
|
btree_node_lock_exclusive (iter);
|
|
version_lock_unlock_exclusive (&(t->root_lock));
|
|
if (!iter)
|
|
return NULL;
|
|
|
|
// Same strategy as with insert, walk down with lock coupling and
|
|
// merge eagerly.
|
|
while (btree_node_is_inner (iter))
|
|
{
|
|
unsigned slot = btree_node_find_inner_slot (iter, base);
|
|
struct btree_node *next = iter->content.children[slot].child;
|
|
btree_node_lock_exclusive (next);
|
|
if (btree_node_needs_merge (next))
|
|
{
|
|
// Use eager merges to avoid lock coupling up.
|
|
iter = btree_merge_node (t, slot, iter, base);
|
|
}
|
|
else
|
|
{
|
|
btree_node_unlock_exclusive (iter);
|
|
iter = next;
|
|
}
|
|
}
|
|
|
|
// Remove existing entry.
|
|
unsigned slot = btree_node_find_leaf_slot (iter, base);
|
|
if ((slot >= iter->entry_count) || (iter->content.entries[slot].base != base))
|
|
{
|
|
// Not found, this should never happen.
|
|
btree_node_unlock_exclusive (iter);
|
|
return NULL;
|
|
}
|
|
struct object *ob = iter->content.entries[slot].ob;
|
|
for (unsigned index = slot; index + 1 < iter->entry_count; ++index)
|
|
iter->content.entries[index] = iter->content.entries[index + 1];
|
|
iter->entry_count--;
|
|
btree_node_unlock_exclusive (iter);
|
|
return ob;
|
|
}
|
|
|
|
// Find the corresponding entry for the given address.
|
|
static struct object *
|
|
btree_lookup (const struct btree *t, uintptr_type target_addr)
|
|
{
|
|
// Within this function many loads are relaxed atomic loads.
|
|
// Use a macro to keep the code reasonable.
|
|
#define RLOAD(x) __atomic_load_n (&(x), __ATOMIC_RELAXED)
|
|
|
|
// For targets where unwind info is usually not registered through these
|
|
// APIs anymore, avoid any sequential consistent atomics.
|
|
// Use relaxed MO here, it is up to the app to ensure that the library
|
|
// loading/initialization happens-before using that library in other
|
|
// threads (in particular unwinding with that library's functions
|
|
// appearing in the backtraces). Calling that library's functions
|
|
// without waiting for the library to initialize would be racy.
|
|
if (__builtin_expect (!RLOAD (t->root), 1))
|
|
return NULL;
|
|
|
|
// The unwinding tables are mostly static, they only change when
|
|
// frames are added or removed. This makes it extremely unlikely that they
|
|
// change during a given unwinding sequence. Thus, we optimize for the
|
|
// contention free case and use optimistic lock coupling. This does not
|
|
// require any writes to shared state, instead we validate every read. It is
|
|
// important that we do not trust any value that we have read until we call
|
|
// validate again. Data can change at arbitrary points in time, thus we always
|
|
// copy something into a local variable and validate again before acting on
|
|
// the read. In the unlikely event that we encounter a concurrent change we
|
|
// simply restart and try again.
|
|
|
|
restart:
|
|
struct btree_node *iter;
|
|
uintptr_type lock;
|
|
{
|
|
// Accessing the root node requires defending against concurrent pointer
|
|
// changes Thus we couple rootLock -> lock on root node -> validate rootLock
|
|
if (!version_lock_lock_optimistic (&(t->root_lock), &lock))
|
|
goto restart;
|
|
iter = RLOAD (t->root);
|
|
if (!version_lock_validate (&(t->root_lock), lock))
|
|
goto restart;
|
|
if (!iter)
|
|
return NULL;
|
|
uintptr_type child_lock;
|
|
if ((!btree_node_lock_optimistic (iter, &child_lock))
|
|
|| (!version_lock_validate (&(t->root_lock), lock)))
|
|
goto restart;
|
|
lock = child_lock;
|
|
}
|
|
|
|
// Now we can walk down towards the right leaf node.
|
|
while (true)
|
|
{
|
|
enum node_type type = RLOAD (iter->type);
|
|
unsigned entry_count = RLOAD (iter->entry_count);
|
|
if (!btree_node_validate (iter, lock))
|
|
goto restart;
|
|
if (!entry_count)
|
|
return NULL;
|
|
|
|
if (type == btree_node_inner)
|
|
{
|
|
// We cannot call find_inner_slot here because we need (relaxed)
|
|
// atomic reads here.
|
|
unsigned slot = 0;
|
|
while (
|
|
((slot + 1) < entry_count)
|
|
&& (RLOAD (iter->content.children[slot].separator) < target_addr))
|
|
++slot;
|
|
struct btree_node *child = RLOAD (iter->content.children[slot].child);
|
|
if (!btree_node_validate (iter, lock))
|
|
goto restart;
|
|
|
|
// The node content can change at any point in time, thus we must
|
|
// interleave parent and child checks.
|
|
uintptr_type child_lock;
|
|
if (!btree_node_lock_optimistic (child, &child_lock))
|
|
goto restart;
|
|
if (!btree_node_validate (iter, lock))
|
|
goto restart; // make sure we still point to the correct node after
|
|
// acquiring the optimistic lock.
|
|
|
|
// Go down
|
|
iter = child;
|
|
lock = child_lock;
|
|
}
|
|
else
|
|
{
|
|
// We cannot call find_leaf_slot here because we need (relaxed)
|
|
// atomic reads here.
|
|
unsigned slot = 0;
|
|
while (((slot + 1) < entry_count)
|
|
&& (RLOAD (iter->content.entries[slot].base)
|
|
+ RLOAD (iter->content.entries[slot].size)
|
|
<= target_addr))
|
|
++slot;
|
|
struct leaf_entry entry;
|
|
entry.base = RLOAD (iter->content.entries[slot].base);
|
|
entry.size = RLOAD (iter->content.entries[slot].size);
|
|
entry.ob = RLOAD (iter->content.entries[slot].ob);
|
|
if (!btree_node_validate (iter, lock))
|
|
goto restart;
|
|
|
|
// Check if we have a hit.
|
|
if ((entry.base <= target_addr)
|
|
&& (target_addr < entry.base + entry.size))
|
|
{
|
|
return entry.ob;
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
#undef RLOAD
|
|
}
|
|
|
|
#endif /* unwind-dw2-btree.h */
|