2022-03-01 21:57:35 +01:00
|
|
|
/* Lock-free btree for manually registered unwind frames. */
|
2025-01-02 11:59:57 +01:00
|
|
|
/* Copyright (C) 2022-2025 Free Software Foundation, Inc.
|
2022-03-01 21:57:35 +01:00
|
|
|
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
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
// The lock itself. The lowest bit indicates an exclusive lock,
|
|
|
|
// the second bit indicates waiting threads. All other bits are
|
2022-03-01 21:57:35 +01:00
|
|
|
// used as counter to recognize changes.
|
|
|
|
// Overflows are okay here, we must only prevent overflow to the
|
|
|
|
// same value within one lock_optimistic/validate
|
2025-03-10 09:33:55 +01:00
|
|
|
// range. Even on 32 bit platforms that would require 1 billion
|
2022-03-01 21:57:35 +01:00
|
|
|
// frame registrations within the time span of a few assembler
|
|
|
|
// instructions.
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type version_lock;
|
2022-03-01 21:57:35 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
#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)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
uintptr_type state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
if (state & 1)
|
|
|
|
return false;
|
2025-03-10 09:33:55 +01:00
|
|
|
return __atomic_compare_exchange_n (&vl->version_lock, &state, state | 1,
|
2022-03-01 21:57:35 +01:00
|
|
|
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.
|
2025-03-10 09:33:55 +01:00
|
|
|
uintptr_type state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
if (!(state & 1))
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
if (__atomic_compare_exchange_n (&vl->version_lock, &state, state | 1,
|
2022-03-01 21:57:35 +01:00
|
|
|
false, __ATOMIC_SEQ_CST,
|
|
|
|
__ATOMIC_SEQ_CST))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We did get contention, wait properly.
|
|
|
|
#ifdef __GTHREAD_HAS_COND
|
|
|
|
__gthread_mutex_lock (&version_lock_mutex);
|
2025-03-10 09:33:55 +01:00
|
|
|
state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Check if the lock is still held.
|
|
|
|
if (!(state & 1))
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
if (__atomic_compare_exchange_n (&vl->version_lock, &state,
|
2022-03-01 21:57:35 +01:00
|
|
|
state | 1, false, __ATOMIC_SEQ_CST,
|
|
|
|
__ATOMIC_SEQ_CST))
|
|
|
|
{
|
|
|
|
__gthread_mutex_unlock (&version_lock_mutex);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
2025-03-10 09:33:55 +01:00
|
|
|
continue;
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Register waiting thread.
|
|
|
|
if (!(state & 2))
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
if (!__atomic_compare_exchange_n (&vl->version_lock, &state,
|
2022-03-01 21:57:35 +01:00
|
|
|
state | 2, false, __ATOMIC_SEQ_CST,
|
|
|
|
__ATOMIC_SEQ_CST))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// And sleep.
|
|
|
|
__gthread_cond_wait (&version_lock_cond, &version_lock_mutex);
|
2025-03-10 09:33:55 +01:00
|
|
|
state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
#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)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
// 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);
|
2022-03-01 21:57:35 +01:00
|
|
|
|
|
|
|
#ifdef __GTHREAD_HAS_COND
|
|
|
|
if (state & 2)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
// Wake up waiting threads. This should be extremely rare.
|
2022-03-01 21:57:35 +01:00
|
|
|
__gthread_mutex_lock (&version_lock_mutex);
|
|
|
|
__gthread_cond_broadcast (&version_lock_cond);
|
|
|
|
__gthread_mutex_unlock (&version_lock_mutex);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Acquire an optimistic "lock". Note that this does not lock at all, it
|
2022-03-01 21:57:35 +01:00
|
|
|
// only allows for validation later.
|
|
|
|
static inline bool
|
2022-09-18 11:31:01 +02:00
|
|
|
version_lock_lock_optimistic (const struct version_lock *vl, uintptr_type *lock)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
uintptr_type state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
*lock = state;
|
|
|
|
|
|
|
|
// Acquiring the lock fails when there is currently an exclusive lock.
|
|
|
|
return !(state & 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate a previously acquired "lock".
|
|
|
|
static inline bool
|
2022-09-18 11:31:01 +02:00
|
|
|
version_lock_validate (const struct version_lock *vl, uintptr_type lock)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// 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.
|
2025-03-10 09:33:55 +01:00
|
|
|
uintptr_type state = __atomic_load_n (&vl->version_lock, __ATOMIC_SEQ_CST);
|
|
|
|
return state == lock;
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// The largest possible separator value.
|
2025-03-10 09:33:55 +01:00
|
|
|
static const uintptr_type max_separator = ~(uintptr_type) 0;
|
2022-03-01 21:57:35 +01:00
|
|
|
|
|
|
|
struct btree_node;
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Inner entry. The child tree contains all entries <= separator.
|
2022-03-01 21:57:35 +01:00
|
|
|
struct inner_entry
|
|
|
|
{
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type separator;
|
2022-03-01 21:57:35 +01:00
|
|
|
struct btree_node *child;
|
|
|
|
};
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Leaf entry. Stores an object entry.
|
2022-03-01 21:57:35 +01:00
|
|
|
struct leaf_entry
|
|
|
|
{
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type base, size;
|
2022-03-01 21:57:35 +01:00
|
|
|
struct object *ob;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Node types.
|
|
|
|
enum node_type
|
|
|
|
{
|
|
|
|
btree_node_inner,
|
|
|
|
btree_node_leaf,
|
|
|
|
btree_node_free
|
|
|
|
};
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Node sizes. Chosen such that the result size is roughly 256 bytes.
|
2022-03-01 21:57:35 +01:00
|
|
|
#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)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
return n->entry_count < (btree_node_is_inner (n) ? max_fanout_inner / 2
|
|
|
|
: max_fanout_leaf / 2);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the fence key for inner nodes.
|
2022-09-18 11:31:01 +02:00
|
|
|
static inline uintptr_type
|
2022-03-01 21:57:35 +01:00
|
|
|
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
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_node_find_inner_slot (const struct btree_node *n, uintptr_type value)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
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
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_node_find_leaf_slot (const struct btree_node *n, uintptr_type value)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
return version_lock_try_lock_exclusive (&n->version_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lock the node exclusive, blocking as needed.
|
|
|
|
static inline void
|
|
|
|
btree_node_lock_exclusive (struct btree_node *n)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
version_lock_lock_exclusive (&n->version_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Release a locked node and increase the version lock.
|
|
|
|
static inline void
|
|
|
|
btree_node_unlock_exclusive (struct btree_node *n)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
version_lock_unlock_exclusive (&n->version_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Acquire an optimistic "lock". Note that this does not lock at all, it
|
2022-03-01 21:57:35 +01:00
|
|
|
// only allows for validation later.
|
|
|
|
static inline bool
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_node_lock_optimistic (const struct btree_node *n, uintptr_type *lock)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
return version_lock_lock_optimistic (&n->version_lock, lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate a previously acquire lock.
|
|
|
|
static inline bool
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_node_validate (const struct btree_node *n, uintptr_type lock)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
return version_lock_validate (&n->version_lock, lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Insert a new separator after splitting.
|
|
|
|
static void
|
|
|
|
btree_node_update_separator_after_split (struct btree_node *n,
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type old_separator,
|
|
|
|
uintptr_type new_separator,
|
2022-03-01 21:57:35 +01:00
|
|
|
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++;
|
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// A btree. Suitable for static initialization, all members are zero at the
|
2022-03-01 21:57:35 +01:00
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Initialize a btree. Not actually used, just for exposition.
|
2022-03-01 21:57:35 +01:00
|
|
|
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
|
2025-03-10 09:33:55 +01:00
|
|
|
= __atomic_exchange_n (&t->root, NULL, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Allocate a node. This node will be returned in locked exclusive state.
|
2022-03-01 21:57:35 +01:00
|
|
|
static struct btree_node *
|
|
|
|
btree_allocate_node (struct btree *t, bool inner)
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Try the free list first.
|
|
|
|
struct btree_node *next_free
|
2025-03-10 09:33:55 +01:00
|
|
|
= __atomic_load_n (&t->free_list, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
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;
|
2025-03-10 09:33:55 +01:00
|
|
|
if (__atomic_compare_exchange_n (&t->free_list, &ex,
|
|
|
|
ex->content.children[0].child,
|
|
|
|
false, __ATOMIC_SEQ_CST,
|
|
|
|
__ATOMIC_SEQ_CST))
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
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
|
2025-03-10 09:33:55 +01:00
|
|
|
= (struct btree_node *) malloc (sizeof (struct btree_node));
|
|
|
|
// Initialize the node in locked state.
|
|
|
|
version_lock_initialize_locked_exclusive (&new_node->version_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
new_node->entry_count = 0;
|
|
|
|
new_node->type = inner ? btree_node_inner : btree_node_leaf;
|
|
|
|
return new_node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Release a node. This node must be currently locked exclusively and will
|
2022-03-01 21:57:35 +01:00
|
|
|
// 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
|
2025-03-10 09:33:55 +01:00
|
|
|
// concurrent readers on that node. Put it in the free list instead.
|
2022-03-01 21:57:35 +01:00
|
|
|
node->type = btree_node_free;
|
|
|
|
struct btree_node *next_free
|
2025-03-10 09:33:55 +01:00
|
|
|
= __atomic_load_n (&t->free_list, __ATOMIC_SEQ_CST);
|
2022-03-01 21:57:35 +01:00
|
|
|
do
|
2025-03-10 09:33:55 +01:00
|
|
|
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));
|
2022-03-01 21:57:35 +01:00
|
|
|
btree_node_unlock_exclusive (node);
|
|
|
|
}
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Recursively release a tree. The btree is by design very shallow, thus
|
2022-03-01 21:57:35 +01:00
|
|
|
// 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
|
2025-03-10 09:33:55 +01:00
|
|
|
// free reads. Thus, we split the root by first moving the content
|
2022-03-01 21:57:35 +01:00
|
|
|
// 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,
|
libgcc: Fix up unwind-dw2-btree.h [PR119151]
The following testcase shows a bug in unwind-dw2-btree.h.
In short, the header provides lock-free btree data structure (so no parent
link on nodes, both insertion and deletion are done in top-down walks
with some locking of just a few nodes at a time so that lookups can notice
concurrent modifications and retry, non-leaf (inner) nodes contain keys
which are initially the base address of the left-most leaf entry of the
following child (or all ones if there is none) minus one, insertion ensures
balancing of the tree to ensure [d/2, d] entries filled through aggressive
splitting if it sees a full tree while walking, deletion performs various
operations like merging neighbour trees, merging into parent or moving some
nodes from neighbour to the current one).
What differs from the textbook implementations is mostly that the leaf nodes
don't include just address as a key, but address range, address + size
(where we don't insert any ranges with zero size) and the lookups can be
performed for any address in the [address, address + size) range. The keys
on inner nodes are still just address-1, so the child covers all nodes
where addr <= key unless it is covered already in children to the left.
The user (static executables or JIT) should always ensure there is no
overlap in between any of the ranges.
In the testcase a bunch of insertions are done, always followed by one
removal, followed by one insertion of a range slightly different from the
removed one. E.g. in the first case [&code[0x50], &code[0x59]] range
is removed and then we insert [&code[0x4c], &code[0x53]] range instead.
This is valid, it doesn't overlap anything. But the problem is that some
non-leaf (inner) one used the &code[0x4f] key (after the 11 insertions
completely correctly). On removal, nothing adjusts the keys on the parent
nodes (it really can't in the top-down only walk, the keys could be many nodes
above it and unlike insertion, removal only knows the start address, doesn't
know the removed size and so will discover it only when reaching the leaf
node which contains it; plus even if it knew the address and size, it still
doesn't know what the second left-most leaf node will be (i.e. the one after
removal)). And on insertion, if nodes aren't split at a level, nothing
adjusts the inner keys either. If a range is inserted and is either fully
bellow key (keys are - 1, so having address + size - 1 being equal to key is
fine) or fully after key (i.e. address > key), it works just fine, but if
the key is in a middle of the range like in this case, &code[0x4f] is in the
middle of the [&code[0x4c], &code[0x53]] range, then insertion works fine
(we only use size on the leaf nodes), and lookup of the addresses below
the key work fine too (i.e. [&code[0x4c], &code[0x4f]] will succeed).
The problem is with lookups after the key (i.e. [&code[0x50, &code[0x53]]),
the lookup looks for them in different children of the btree and doesn't
find an entry and returns NULL.
As users need to ensure non-overlapping entries at any time, the following
patch fixes it by adjusting keys during insertion where we know not just
the address but also size; if we find during the top-down walk a key
which is in the middle of the range being inserted, we simply increase the
key to be equal to address + size - 1 of the range being inserted.
There can't be any existing leaf nodes overlapping the range in correct
programs and the btree rebalancing done on deletion ensures we don't have
any empty nodes which would also cause problems.
The patch adjusts the keys in two spots, once for the current node being
walked (the last hunk in the header, with large comment trying to explain
it) and once during inner node splitting in a parent node if we'd otherwise
try to add that key in the middle of the range being inserted into the
parent node (in that case it would be missed in the last hunk).
The testcase covers both of those spots, so succeeds with GCC 12 (which
didn't have btrees) and fails with vanilla GCC trunk and also fails if
either the
if (fence < base + size - 1)
fence = iter->content.children[slot].separator = base + size - 1;
or
if (left_fence >= target && left_fence < target + size - 1)
left_fence = target + size - 1;
hunk is removed (of course, only with the current node sizes, i.e. up to
15 children of inner nodes and up to 10 entries in leaf nodes).
2025-03-10 Jakub Jelinek <jakub@redhat.com>
Michael Leuchtenburg <michael@slashhome.org>
PR libgcc/119151
* unwind-dw2-btree.h (btree_split_inner): Add size argument. If
left_fence is in the middle of [target,target + size - 1] range,
increase it to target + size - 1.
(btree_insert): Adjust btree_split_inner caller. If fence is smaller
than base + size - 1, increase it and separator of the slot to
base + size - 1.
* gcc.dg/pr119151.c: New test.
2025-03-10 10:34:00 +01:00
|
|
|
struct btree_node **parent, uintptr_type target,
|
|
|
|
uintptr_type size)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// Check for the root.
|
|
|
|
btree_handle_root_split (t, inner, parent);
|
|
|
|
|
|
|
|
// Create two inner node.
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type right_fence = btree_node_get_fence_key (*inner);
|
2022-03-01 21:57:35 +01:00
|
|
|
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;
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type left_fence = btree_node_get_fence_key (left_inner);
|
libgcc: Fix up unwind-dw2-btree.h [PR119151]
The following testcase shows a bug in unwind-dw2-btree.h.
In short, the header provides lock-free btree data structure (so no parent
link on nodes, both insertion and deletion are done in top-down walks
with some locking of just a few nodes at a time so that lookups can notice
concurrent modifications and retry, non-leaf (inner) nodes contain keys
which are initially the base address of the left-most leaf entry of the
following child (or all ones if there is none) minus one, insertion ensures
balancing of the tree to ensure [d/2, d] entries filled through aggressive
splitting if it sees a full tree while walking, deletion performs various
operations like merging neighbour trees, merging into parent or moving some
nodes from neighbour to the current one).
What differs from the textbook implementations is mostly that the leaf nodes
don't include just address as a key, but address range, address + size
(where we don't insert any ranges with zero size) and the lookups can be
performed for any address in the [address, address + size) range. The keys
on inner nodes are still just address-1, so the child covers all nodes
where addr <= key unless it is covered already in children to the left.
The user (static executables or JIT) should always ensure there is no
overlap in between any of the ranges.
In the testcase a bunch of insertions are done, always followed by one
removal, followed by one insertion of a range slightly different from the
removed one. E.g. in the first case [&code[0x50], &code[0x59]] range
is removed and then we insert [&code[0x4c], &code[0x53]] range instead.
This is valid, it doesn't overlap anything. But the problem is that some
non-leaf (inner) one used the &code[0x4f] key (after the 11 insertions
completely correctly). On removal, nothing adjusts the keys on the parent
nodes (it really can't in the top-down only walk, the keys could be many nodes
above it and unlike insertion, removal only knows the start address, doesn't
know the removed size and so will discover it only when reaching the leaf
node which contains it; plus even if it knew the address and size, it still
doesn't know what the second left-most leaf node will be (i.e. the one after
removal)). And on insertion, if nodes aren't split at a level, nothing
adjusts the inner keys either. If a range is inserted and is either fully
bellow key (keys are - 1, so having address + size - 1 being equal to key is
fine) or fully after key (i.e. address > key), it works just fine, but if
the key is in a middle of the range like in this case, &code[0x4f] is in the
middle of the [&code[0x4c], &code[0x53]] range, then insertion works fine
(we only use size on the leaf nodes), and lookup of the addresses below
the key work fine too (i.e. [&code[0x4c], &code[0x4f]] will succeed).
The problem is with lookups after the key (i.e. [&code[0x50, &code[0x53]]),
the lookup looks for them in different children of the btree and doesn't
find an entry and returns NULL.
As users need to ensure non-overlapping entries at any time, the following
patch fixes it by adjusting keys during insertion where we know not just
the address but also size; if we find during the top-down walk a key
which is in the middle of the range being inserted, we simply increase the
key to be equal to address + size - 1 of the range being inserted.
There can't be any existing leaf nodes overlapping the range in correct
programs and the btree rebalancing done on deletion ensures we don't have
any empty nodes which would also cause problems.
The patch adjusts the keys in two spots, once for the current node being
walked (the last hunk in the header, with large comment trying to explain
it) and once during inner node splitting in a parent node if we'd otherwise
try to add that key in the middle of the range being inserted into the
parent node (in that case it would be missed in the last hunk).
The testcase covers both of those spots, so succeeds with GCC 12 (which
didn't have btrees) and fails with vanilla GCC trunk and also fails if
either the
if (fence < base + size - 1)
fence = iter->content.children[slot].separator = base + size - 1;
or
if (left_fence >= target && left_fence < target + size - 1)
left_fence = target + size - 1;
hunk is removed (of course, only with the current node sizes, i.e. up to
15 children of inner nodes and up to 10 entries in leaf nodes).
2025-03-10 Jakub Jelinek <jakub@redhat.com>
Michael Leuchtenburg <michael@slashhome.org>
PR libgcc/119151
* unwind-dw2-btree.h (btree_split_inner): Add size argument. If
left_fence is in the middle of [target,target + size - 1] range,
increase it to target + size - 1.
(btree_insert): Adjust btree_split_inner caller. If fence is smaller
than base + size - 1, increase it and separator of the slot to
base + size - 1.
* gcc.dg/pr119151.c: New test.
2025-03-10 10:34:00 +01:00
|
|
|
if (left_fence >= target && left_fence < target + size - 1)
|
|
|
|
// See the PR119151 comment in btree_insert.
|
|
|
|
left_fence = target + size - 1;
|
2022-03-01 21:57:35 +01:00
|
|
|
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,
|
2022-09-18 11:31:01 +02:00
|
|
|
struct btree_node **parent, uintptr_type fence,
|
|
|
|
uintptr_type target)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// Check for the root.
|
|
|
|
btree_handle_root_split (t, leaf, parent);
|
|
|
|
|
|
|
|
// Create two leaf nodes.
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type right_fence = fence;
|
2022-03-01 21:57:35 +01:00
|
|
|
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;
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type left_fence = right_leaf->content.entries[0].base - 1;
|
2022-03-01 21:57:35 +01:00
|
|
|
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,
|
2022-09-18 11:31:01 +02:00
|
|
|
struct btree_node *parent, uintptr_type target)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
// Choose the emptiest neighbor and lock both. The target child is already
|
2022-03-01 21:57:35 +01:00
|
|
|
// locked.
|
|
|
|
unsigned left_slot;
|
|
|
|
struct btree_node *left_node, *right_node;
|
2025-03-10 09:33:55 +01:00
|
|
|
if (child_slot == 0
|
|
|
|
|| (child_slot + 1 < parent->entry_count
|
2022-03-01 21:57:35 +01:00
|
|
|
&& (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)
|
|
|
|
{
|
2025-03-10 09:33:55 +01:00
|
|
|
// Merge children into parent. This can only happen at the root.
|
2022-03-01 21:57:35 +01:00
|
|
|
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;
|
|
|
|
}
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type left_fence;
|
2022-03-01 21:57:35 +01:00
|
|
|
if (btree_node_is_leaf (left_node))
|
2025-03-10 09:33:55 +01:00
|
|
|
left_fence = right_node->content.entries[0].base - 1;
|
2022-03-01 21:57:35 +01:00
|
|
|
else
|
2025-03-10 09:33:55 +01:00
|
|
|
left_fence = btree_node_get_fence_key (left_node);
|
2022-03-01 21:57:35 +01:00
|
|
|
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
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_insert (struct btree *t, uintptr_type base, uintptr_type size,
|
2022-03-01 21:57:35 +01:00
|
|
|
struct object *ob)
|
|
|
|
{
|
|
|
|
// Sanity check.
|
|
|
|
if (!size)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Access the root.
|
|
|
|
struct btree_node *iter, *parent = NULL;
|
2025-03-10 09:33:55 +01:00
|
|
|
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);
|
2022-03-01 21:57:35 +01:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type fence = max_separator;
|
2022-03-01 21:57:35 +01:00
|
|
|
while (btree_node_is_inner (iter))
|
|
|
|
{
|
|
|
|
// Use eager splits to avoid lock coupling up.
|
|
|
|
if (iter->entry_count == max_fanout_inner)
|
libgcc: Fix up unwind-dw2-btree.h [PR119151]
The following testcase shows a bug in unwind-dw2-btree.h.
In short, the header provides lock-free btree data structure (so no parent
link on nodes, both insertion and deletion are done in top-down walks
with some locking of just a few nodes at a time so that lookups can notice
concurrent modifications and retry, non-leaf (inner) nodes contain keys
which are initially the base address of the left-most leaf entry of the
following child (or all ones if there is none) minus one, insertion ensures
balancing of the tree to ensure [d/2, d] entries filled through aggressive
splitting if it sees a full tree while walking, deletion performs various
operations like merging neighbour trees, merging into parent or moving some
nodes from neighbour to the current one).
What differs from the textbook implementations is mostly that the leaf nodes
don't include just address as a key, but address range, address + size
(where we don't insert any ranges with zero size) and the lookups can be
performed for any address in the [address, address + size) range. The keys
on inner nodes are still just address-1, so the child covers all nodes
where addr <= key unless it is covered already in children to the left.
The user (static executables or JIT) should always ensure there is no
overlap in between any of the ranges.
In the testcase a bunch of insertions are done, always followed by one
removal, followed by one insertion of a range slightly different from the
removed one. E.g. in the first case [&code[0x50], &code[0x59]] range
is removed and then we insert [&code[0x4c], &code[0x53]] range instead.
This is valid, it doesn't overlap anything. But the problem is that some
non-leaf (inner) one used the &code[0x4f] key (after the 11 insertions
completely correctly). On removal, nothing adjusts the keys on the parent
nodes (it really can't in the top-down only walk, the keys could be many nodes
above it and unlike insertion, removal only knows the start address, doesn't
know the removed size and so will discover it only when reaching the leaf
node which contains it; plus even if it knew the address and size, it still
doesn't know what the second left-most leaf node will be (i.e. the one after
removal)). And on insertion, if nodes aren't split at a level, nothing
adjusts the inner keys either. If a range is inserted and is either fully
bellow key (keys are - 1, so having address + size - 1 being equal to key is
fine) or fully after key (i.e. address > key), it works just fine, but if
the key is in a middle of the range like in this case, &code[0x4f] is in the
middle of the [&code[0x4c], &code[0x53]] range, then insertion works fine
(we only use size on the leaf nodes), and lookup of the addresses below
the key work fine too (i.e. [&code[0x4c], &code[0x4f]] will succeed).
The problem is with lookups after the key (i.e. [&code[0x50, &code[0x53]]),
the lookup looks for them in different children of the btree and doesn't
find an entry and returns NULL.
As users need to ensure non-overlapping entries at any time, the following
patch fixes it by adjusting keys during insertion where we know not just
the address but also size; if we find during the top-down walk a key
which is in the middle of the range being inserted, we simply increase the
key to be equal to address + size - 1 of the range being inserted.
There can't be any existing leaf nodes overlapping the range in correct
programs and the btree rebalancing done on deletion ensures we don't have
any empty nodes which would also cause problems.
The patch adjusts the keys in two spots, once for the current node being
walked (the last hunk in the header, with large comment trying to explain
it) and once during inner node splitting in a parent node if we'd otherwise
try to add that key in the middle of the range being inserted into the
parent node (in that case it would be missed in the last hunk).
The testcase covers both of those spots, so succeeds with GCC 12 (which
didn't have btrees) and fails with vanilla GCC trunk and also fails if
either the
if (fence < base + size - 1)
fence = iter->content.children[slot].separator = base + size - 1;
or
if (left_fence >= target && left_fence < target + size - 1)
left_fence = target + size - 1;
hunk is removed (of course, only with the current node sizes, i.e. up to
15 children of inner nodes and up to 10 entries in leaf nodes).
2025-03-10 Jakub Jelinek <jakub@redhat.com>
Michael Leuchtenburg <michael@slashhome.org>
PR libgcc/119151
* unwind-dw2-btree.h (btree_split_inner): Add size argument. If
left_fence is in the middle of [target,target + size - 1] range,
increase it to target + size - 1.
(btree_insert): Adjust btree_split_inner caller. If fence is smaller
than base + size - 1, increase it and separator of the slot to
base + size - 1.
* gcc.dg/pr119151.c: New test.
2025-03-10 10:34:00 +01:00
|
|
|
btree_split_inner (t, &iter, &parent, base, size);
|
2022-03-01 21:57:35 +01:00
|
|
|
|
|
|
|
unsigned slot = btree_node_find_inner_slot (iter, base);
|
|
|
|
if (parent)
|
|
|
|
btree_node_unlock_exclusive (parent);
|
|
|
|
parent = iter;
|
|
|
|
fence = iter->content.children[slot].separator;
|
libgcc: Fix up unwind-dw2-btree.h [PR119151]
The following testcase shows a bug in unwind-dw2-btree.h.
In short, the header provides lock-free btree data structure (so no parent
link on nodes, both insertion and deletion are done in top-down walks
with some locking of just a few nodes at a time so that lookups can notice
concurrent modifications and retry, non-leaf (inner) nodes contain keys
which are initially the base address of the left-most leaf entry of the
following child (or all ones if there is none) minus one, insertion ensures
balancing of the tree to ensure [d/2, d] entries filled through aggressive
splitting if it sees a full tree while walking, deletion performs various
operations like merging neighbour trees, merging into parent or moving some
nodes from neighbour to the current one).
What differs from the textbook implementations is mostly that the leaf nodes
don't include just address as a key, but address range, address + size
(where we don't insert any ranges with zero size) and the lookups can be
performed for any address in the [address, address + size) range. The keys
on inner nodes are still just address-1, so the child covers all nodes
where addr <= key unless it is covered already in children to the left.
The user (static executables or JIT) should always ensure there is no
overlap in between any of the ranges.
In the testcase a bunch of insertions are done, always followed by one
removal, followed by one insertion of a range slightly different from the
removed one. E.g. in the first case [&code[0x50], &code[0x59]] range
is removed and then we insert [&code[0x4c], &code[0x53]] range instead.
This is valid, it doesn't overlap anything. But the problem is that some
non-leaf (inner) one used the &code[0x4f] key (after the 11 insertions
completely correctly). On removal, nothing adjusts the keys on the parent
nodes (it really can't in the top-down only walk, the keys could be many nodes
above it and unlike insertion, removal only knows the start address, doesn't
know the removed size and so will discover it only when reaching the leaf
node which contains it; plus even if it knew the address and size, it still
doesn't know what the second left-most leaf node will be (i.e. the one after
removal)). And on insertion, if nodes aren't split at a level, nothing
adjusts the inner keys either. If a range is inserted and is either fully
bellow key (keys are - 1, so having address + size - 1 being equal to key is
fine) or fully after key (i.e. address > key), it works just fine, but if
the key is in a middle of the range like in this case, &code[0x4f] is in the
middle of the [&code[0x4c], &code[0x53]] range, then insertion works fine
(we only use size on the leaf nodes), and lookup of the addresses below
the key work fine too (i.e. [&code[0x4c], &code[0x4f]] will succeed).
The problem is with lookups after the key (i.e. [&code[0x50, &code[0x53]]),
the lookup looks for them in different children of the btree and doesn't
find an entry and returns NULL.
As users need to ensure non-overlapping entries at any time, the following
patch fixes it by adjusting keys during insertion where we know not just
the address but also size; if we find during the top-down walk a key
which is in the middle of the range being inserted, we simply increase the
key to be equal to address + size - 1 of the range being inserted.
There can't be any existing leaf nodes overlapping the range in correct
programs and the btree rebalancing done on deletion ensures we don't have
any empty nodes which would also cause problems.
The patch adjusts the keys in two spots, once for the current node being
walked (the last hunk in the header, with large comment trying to explain
it) and once during inner node splitting in a parent node if we'd otherwise
try to add that key in the middle of the range being inserted into the
parent node (in that case it would be missed in the last hunk).
The testcase covers both of those spots, so succeeds with GCC 12 (which
didn't have btrees) and fails with vanilla GCC trunk and also fails if
either the
if (fence < base + size - 1)
fence = iter->content.children[slot].separator = base + size - 1;
or
if (left_fence >= target && left_fence < target + size - 1)
left_fence = target + size - 1;
hunk is removed (of course, only with the current node sizes, i.e. up to
15 children of inner nodes and up to 10 entries in leaf nodes).
2025-03-10 Jakub Jelinek <jakub@redhat.com>
Michael Leuchtenburg <michael@slashhome.org>
PR libgcc/119151
* unwind-dw2-btree.h (btree_split_inner): Add size argument. If
left_fence is in the middle of [target,target + size - 1] range,
increase it to target + size - 1.
(btree_insert): Adjust btree_split_inner caller. If fence is smaller
than base + size - 1, increase it and separator of the slot to
base + size - 1.
* gcc.dg/pr119151.c: New test.
2025-03-10 10:34:00 +01:00
|
|
|
if (fence < base + size - 1)
|
|
|
|
// The separator was set to the base - 1 of the leftmost leaf child
|
|
|
|
// at some point but such an entry could have been removed afterwards.
|
|
|
|
// As both insertion and removal are just walking down the tree with
|
|
|
|
// only a few current nodes locked at a time, updating the separator
|
|
|
|
// on removal is not possible, especially because btree_remove does
|
|
|
|
// not know the size until it reaches leaf node. We must ensure that
|
|
|
|
// the separator is not in a middle of some entry though, as
|
|
|
|
// btree_lookup can look up any address in the entry's range and if
|
|
|
|
// the separator is in the middle, addresses below it or equal to it
|
|
|
|
// would be found while addresses above it would result in failed
|
|
|
|
// lookup. Update the separator now. Assumption that users
|
|
|
|
// ensure no overlapping registered ranges, there should be no
|
|
|
|
// current entry for any address in the range. See PR119151.
|
|
|
|
fence = iter->content.children[slot].separator = base + size - 1;
|
2022-03-01 21:57:35 +01:00
|
|
|
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);
|
2025-03-10 09:33:55 +01:00
|
|
|
if (slot < iter->entry_count && iter->content.entries[slot].base == base)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// 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];
|
2025-03-10 09:33:55 +01:00
|
|
|
struct leaf_entry *e = &iter->content.entries[slot];
|
2022-03-01 21:57:35 +01:00
|
|
|
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 *
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_remove (struct btree *t, uintptr_type base)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// Access the root.
|
2025-03-10 09:33:55 +01:00
|
|
|
version_lock_lock_exclusive (&t->root_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
struct btree_node *iter = t->root;
|
|
|
|
if (iter)
|
|
|
|
btree_node_lock_exclusive (iter);
|
2025-03-10 09:33:55 +01:00
|
|
|
version_lock_unlock_exclusive (&t->root_lock);
|
2022-03-01 21:57:35 +01:00
|
|
|
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))
|
2025-03-10 09:33:55 +01:00
|
|
|
// Use eager merges to avoid lock coupling up.
|
|
|
|
iter = btree_merge_node (t, slot, iter, base);
|
2022-03-01 21:57:35 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
btree_node_unlock_exclusive (iter);
|
|
|
|
iter = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove existing entry.
|
|
|
|
unsigned slot = btree_node_find_leaf_slot (iter, base);
|
2025-03-10 09:33:55 +01:00
|
|
|
if (slot >= iter->entry_count || iter->content.entries[slot].base != base)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// 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 *
|
2022-09-18 11:31:01 +02:00
|
|
|
btree_lookup (const struct btree *t, uintptr_type target_addr)
|
2022-03-01 21:57:35 +01:00
|
|
|
{
|
|
|
|
// 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
|
2025-03-10 09:33:55 +01:00
|
|
|
// 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
|
2022-03-01 21:57:35 +01:00
|
|
|
// important that we do not trust any value that we have read until we call
|
2025-03-10 09:33:55 +01:00
|
|
|
// validate again. Data can change at arbitrary points in time, thus we always
|
2022-03-01 21:57:35 +01:00
|
|
|
// copy something into a local variable and validate again before acting on
|
2025-03-10 09:33:55 +01:00
|
|
|
// the read. In the unlikely event that we encounter a concurrent change we
|
2022-03-01 21:57:35 +01:00
|
|
|
// simply restart and try again.
|
|
|
|
|
|
|
|
restart:
|
|
|
|
struct btree_node *iter;
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type lock;
|
2025-03-10 09:33:55 +01:00
|
|
|
// Accessing the root node requires defending against concurrent pointer
|
|
|
|
// changes. Thus we couple root_lock -> lock on root node -> validate
|
|
|
|
// root_lock.
|
|
|
|
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_root_lock;
|
|
|
|
if (!btree_node_lock_optimistic (iter, &child_root_lock)
|
|
|
|
|| !version_lock_validate (&t->root_lock, lock))
|
|
|
|
goto restart;
|
|
|
|
lock = child_root_lock;
|
2022-03-01 21:57:35 +01:00
|
|
|
|
|
|
|
// 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;
|
2025-03-10 09:33:55 +01:00
|
|
|
while (slot + 1 < entry_count
|
|
|
|
&& (RLOAD (iter->content.children[slot].separator)
|
|
|
|
< target_addr))
|
2022-03-01 21:57:35 +01:00
|
|
|
++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.
|
2022-09-18 11:31:01 +02:00
|
|
|
uintptr_type child_lock;
|
2022-03-01 21:57:35 +01:00
|
|
|
if (!btree_node_lock_optimistic (child, &child_lock))
|
|
|
|
goto restart;
|
|
|
|
if (!btree_node_validate (iter, lock))
|
2025-03-10 09:33:55 +01:00
|
|
|
goto restart; // Make sure we still point to the correct node after
|
2022-03-01 21:57:35 +01:00
|
|
|
// acquiring the optimistic lock.
|
|
|
|
|
2025-03-10 09:33:55 +01:00
|
|
|
// Go down.
|
2022-03-01 21:57:35 +01:00
|
|
|
iter = child;
|
|
|
|
lock = child_lock;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We cannot call find_leaf_slot here because we need (relaxed)
|
|
|
|
// atomic reads here.
|
|
|
|
unsigned slot = 0;
|
2025-03-10 09:33:55 +01:00
|
|
|
while (slot + 1 < entry_count
|
2022-03-01 21:57:35 +01:00
|
|
|
&& (RLOAD (iter->content.entries[slot].base)
|
2025-03-10 09:33:55 +01:00
|
|
|
+ RLOAD (iter->content.entries[slot].size)
|
2022-03-01 21:57:35 +01:00
|
|
|
<= 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.
|
2025-03-10 09:33:55 +01:00
|
|
|
if (entry.base <= target_addr
|
|
|
|
&& target_addr < entry.base + entry.size)
|
|
|
|
return entry.ob;
|
2022-03-01 21:57:35 +01:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#undef RLOAD
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* unwind-dw2-btree.h */
|