From: Jason Merrill <jason@redhat.com>
To: Thomas Neumann <thomas.neumann@in.tum.de>, gcc-patches@gcc.gnu.org
Cc: Florian Weimer <fweimer@redhat.com>,
Jakub Jelinek <jakub@redhat.com>,
Jonathan Wakely <jwakely.gcc@gmail.com>,
Thomas Rodgers <trodgers@redhat.com>
Subject: Re: [PATCH v4] eliminate mutex in fast path of __register_frame
Date: Fri, 16 Sep 2022 16:49:17 +0200 [thread overview]
Message-ID: <0f94cd2c-53e3-df07-ecaf-091266731639@redhat.com> (raw)
In-Reply-To: <2a4776b9-9271-bb3c-a626-d5ec22dae6f3@in.tum.de>
On 9/16/22 06:19, Thomas Neumann wrote:
> The __register_frame/__deregister_frame functions are used to register
> unwinding frames from JITed code in a sorted list. That list itself
> is protected by object_mutex, which leads to terrible performance
> in multi-threaded code and is somewhat expensive even if single-threaded.
> There was already a fast-path that avoided taking the mutex if no
> frame was registered at all.
>
> This commit eliminates both the mutex and the sorted list from
> the atomic fast path, and replaces it with a btree that uses
> optimistic lock coupling during lookup. This allows for fully parallel
> unwinding and is essential to scale exception handling to large
> core counts.
>
> Changes since v3:
> - Avoid code duplication by adding query mode to classify_object_over_fdes
> - Adjust all comments as requested
>
> libgcc/ChangeLog:
>
> * unwind-dw2-fde.c (release_registered_frames): Cleanup at
> shutdown.
> (__register_frame_info_table_bases): Use btree in atomic fast
> path.
> (__deregister_frame_info_bases): Likewise.
> (_Unwind_Find_FDE): Likewise.
> (base_from_object): Make parameter const.
> (classify_object_over_fdes): Add query-only mode.
> (get_pc_range): Compute PC range for lookup.
> * unwind-dw2-fde.h (last_fde): Make parameter const.
> * unwind-dw2-btree.h: New file.
> ---
> libgcc/unwind-dw2-btree.h | 953 ++++++++++++++++++++++++++++++++++++++
> libgcc/unwind-dw2-fde.c | 195 ++++++--
> libgcc/unwind-dw2-fde.h | 2 +-
> 3 files changed, 1098 insertions(+), 52 deletions(-)
> create mode 100644 libgcc/unwind-dw2-btree.h
>
> diff --git a/libgcc/unwind-dw2-btree.h b/libgcc/unwind-dw2-btree.h
> new file mode 100644
> index 00000000000..8853f0eab48
> --- /dev/null
> +++ b/libgcc/unwind-dw2-btree.h
> @@ -0,0 +1,953 @@
> +/* Lock-free btree for manually registered unwind frames. */
> +/* Copyright (C) 2022 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_t 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_t 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_t 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_t state = __atomic_load_n (&(vl->version_lock),
> __ATOMIC_SEQ_CST);
> + uintptr_t ns = (state + 4) & (~((uintptr_t) 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_t
> *lock)
> +{
> + uintptr_t 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_t 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_t state = __atomic_load_n (&(vl->version_lock),
> __ATOMIC_SEQ_CST);
> + return (state == lock);
> +}
> +
> +// The largest possible separator value.
> +static const uintptr_t max_separator = ~((uintptr_t) (0));
> +
> +struct btree_node;
> +
> +// Inner entry. The child tree contains all entries <= separator.
> +struct inner_entry
> +{
> + uintptr_t separator;
> + struct btree_node *child;
> +};
> +
> +// Leaf entry. Stores an object entry.
> +struct leaf_entry
> +{
> + uintptr_t 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_t
> +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_t 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_t 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_t *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_t 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_t old_separator,
> + uintptr_t 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_t target)
> +{
> + // Check for the root.
> + btree_handle_root_split (t, inner, parent);
> +
> + // Create two inner node.
> + uintptr_t 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_t 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_t fence, uintptr_t target)
> +{
> + // Check for the root.
> + btree_handle_root_split (t, leaf, parent);
> +
> + // Create two leaf nodes.
> + uintptr_t 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_t 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_t 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_t 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_t base, uintptr_t 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_t 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_t 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_t 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_t 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_t 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_t 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 */
> diff --git a/libgcc/unwind-dw2-fde.c b/libgcc/unwind-dw2-fde.c
> index 8ee55be5675..1165be0c6df 100644
> --- a/libgcc/unwind-dw2-fde.c
> +++ b/libgcc/unwind-dw2-fde.c
> @@ -42,15 +42,34 @@ see the files COPYING3 and COPYING.RUNTIME
> respectively. If not, see
> #endif
> #endif
>
> +#ifdef ATOMIC_FDE_FAST_PATH
> +#include "unwind-dw2-btree.h"
> +
> +static struct btree registered_frames;
> +
> +static void
> +release_registered_frames (void) __attribute__ ((destructor (110)));
> +static void
> +release_registered_frames (void)
> +{
> + /* Release the b-tree and all frames. Frame releases that happen
> later are
> + * silently ignored */
> + btree_destroy (®istered_frames);
> +}
> +
> +static void
> +get_pc_range (const struct object *ob, uintptr_t *range);
> +static void
> +init_object (struct object *ob);
> +
> +#else
> +
> /* The unseen_objects list contains objects that have been registered
> but not yet categorized in any way. The seen_objects list has had
> its pc_begin and count fields initialized at minimum, and is sorted
> by decreasing value of pc_begin. */
> static struct object *unseen_objects;
> static struct object *seen_objects;
> -#ifdef ATOMIC_FDE_FAST_PATH
> -static int any_objects_registered;
> -#endif
>
> #ifdef __GTHREAD_MUTEX_INIT
> static __gthread_mutex_t object_mutex = __GTHREAD_MUTEX_INIT;
> @@ -78,6 +97,7 @@ init_object_mutex_once (void)
> static __gthread_mutex_t object_mutex;
> #endif
> #endif
> +#endif
>
> /* Called from crtbegin.o to register the unwind info for an object. */
>
> @@ -99,23 +119,23 @@ __register_frame_info_bases (const void *begin,
> struct object *ob,
> ob->fde_end = NULL;
> #endif
>
> +#ifdef ATOMIC_FDE_FAST_PATH
> + // Initialize eagerly to avoid locking later
> + init_object (ob);
> +
> + // And register the frame
> + uintptr_t range[2];
> + get_pc_range (ob, range);
> + btree_insert (®istered_frames, range[0], range[1] - range[0], ob);
> +#else
> init_object_mutex_once ();
> __gthread_mutex_lock (&object_mutex);
>
> ob->next = unseen_objects;
> unseen_objects = ob;
> -#ifdef ATOMIC_FDE_FAST_PATH
> - /* Set flag that at least one library has registered FDEs.
> - 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 (!any_objects_registered)
> - __atomic_store_n (&any_objects_registered, 1, __ATOMIC_RELAXED);
> -#endif
>
> __gthread_mutex_unlock (&object_mutex);
> +#endif
> }
>
> void
> @@ -153,23 +173,23 @@ __register_frame_info_table_bases (void *begin,
> struct object *ob,
> ob->s.b.from_array = 1;
> ob->s.b.encoding = DW_EH_PE_omit;
>
> +#ifdef ATOMIC_FDE_FAST_PATH
> + // Initialize eagerly to avoid locking later
> + init_object (ob);
> +
> + // And register the frame
> + uintptr_t range[2];
> + get_pc_range (ob, range);
> + btree_insert (®istered_frames, range[0], range[1] - range[0], ob);
> +#else
> init_object_mutex_once ();
> __gthread_mutex_lock (&object_mutex);
>
> ob->next = unseen_objects;
> unseen_objects = ob;
> -#ifdef ATOMIC_FDE_FAST_PATH
> - /* Set flag that at least one library has registered FDEs.
> - 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 (!any_objects_registered)
> - __atomic_store_n (&any_objects_registered, 1, __ATOMIC_RELAXED);
> -#endif
>
> __gthread_mutex_unlock (&object_mutex);
> +#endif
> }
>
> void
> @@ -200,16 +220,33 @@ __register_frame_table (void *begin)
> void *
> __deregister_frame_info_bases (const void *begin)
> {
> - struct object **p;
> struct object *ob = 0;
>
> /* If .eh_frame is empty, we haven't registered. */
> if ((const uword *) begin == 0 || *(const uword *) begin == 0)
> return ob;
>
> +#ifdef ATOMIC_FDE_FAST_PATH
> + // Find the corresponding PC range
> + struct object lookupob;
> + lookupob.tbase = 0;
> + lookupob.dbase = 0;
> + lookupob.u.single = begin;
> + lookupob.s.i = 0;
> + lookupob.s.b.encoding = DW_EH_PE_omit;
> +#ifdef DWARF2_OBJECT_END_PTR_EXTENSION
> + lookupob.fde_end = NULL;
> +#endif
> + uintptr_t range[2];
> + get_pc_range (&lookupob, range);
> +
> + // And remove
> + ob = btree_remove (®istered_frames, range[0]);
> +#else
> init_object_mutex_once ();
> __gthread_mutex_lock (&object_mutex);
>
> + struct object **p;
> for (p = &unseen_objects; *p ; p = &(*p)->next)
> if ((*p)->u.single == begin)
> {
> @@ -241,6 +278,8 @@ __deregister_frame_info_bases (const void *begin)
>
> out:
> __gthread_mutex_unlock (&object_mutex);
> +#endif
> +
> gcc_assert (ob);
> return (void *) ob;
> }
> @@ -264,7 +303,7 @@ __deregister_frame (void *begin)
> instead of an _Unwind_Context. */
>
> static _Unwind_Ptr
> -base_from_object (unsigned char encoding, struct object *ob)
> +base_from_object (unsigned char encoding, const struct object *ob)
> {
> if (encoding == DW_EH_PE_omit)
> return 0;
> @@ -628,13 +667,17 @@ end_fde_sort (struct object *ob, struct
> fde_accumulator *accu, size_t count)
> }
> }
>
> -\f
> -/* Update encoding, mixed_encoding, and pc_begin for OB for the
> - fde array beginning at THIS_FDE. Return the number of fdes
> - encountered along the way. */
> +/* Inspect the fde array beginning at this_fde. This
> + function can be used either in query mode (RANGE is
> + not null, OB is const), or in update mode (RANGE is
> + null, OB is modified). In query mode the function computes
> + the range of PC values and stores it in rANGE. In
s/rANGE/RANGE/
> + update mode it updates encoding, mixed_encoding, and pc_begin
> + for OB. Return the number of fdes encountered along the way. */
>
> static size_t
> -classify_object_over_fdes (struct object *ob, const fde *this_fde)
> +classify_object_over_fdes (struct object *ob, const fde *this_fde,
> + uintptr_t *range)
> {
> const struct dwarf_cie *last_cie = 0;
> size_t count = 0;
> @@ -644,7 +687,7 @@ classify_object_over_fdes (struct object *ob, const
> fde *this_fde)
> for (; ! last_fde (ob, this_fde); this_fde = next_fde (this_fde))
> {
> const struct dwarf_cie *this_cie;
> - _Unwind_Ptr mask, pc_begin;
> + _Unwind_Ptr mask, pc_begin, pc_range;
>
> /* Skip CIEs. */
> if (this_fde->CIE_delta == 0)
> @@ -660,14 +703,19 @@ classify_object_over_fdes (struct object *ob,
> const fde *this_fde)
> if (encoding == DW_EH_PE_omit)
> return -1;
> base = base_from_object (encoding, ob);
> - if (ob->s.b.encoding == DW_EH_PE_omit)
> - ob->s.b.encoding = encoding;
> - else if (ob->s.b.encoding != encoding)
> - ob->s.b.mixed_encoding = 1;
> + if (!range)
> + {
> + if (ob->s.b.encoding == DW_EH_PE_omit)
> + ob->s.b.encoding = encoding;
> + else if (ob->s.b.encoding != encoding)
> + ob->s.b.mixed_encoding = 1;
> + }
> }
>
> - read_encoded_value_with_base (encoding, base, this_fde->pc_begin,
> - &pc_begin);
> + const unsigned char *p;
> + p = read_encoded_value_with_base (encoding, base,
> this_fde->pc_begin,
> + &pc_begin);
> + read_encoded_value_with_base (encoding & 0x0F, 0, p, &pc_range);
It looks like pc_range is only used in query mode, so how about moving
this read and the declaration of pc_range...
> /* Take care to ignore link-once functions that were removed.
> In these cases, the function address will be NULL, but if
> @@ -683,8 +731,27 @@ classify_object_over_fdes (struct object *ob, const
> fde *this_fde)
> continue;
>
> count += 1;
> - if ((void *) pc_begin < ob->pc_begin)
> - ob->pc_begin = (void *) pc_begin;
> + if (range)
> + {
...in here? OK either way. Thanks again, this is impressive work!
> + _Unwind_Ptr pc_end = pc_begin + pc_range;
> + if ((!range[0]) && (!range[1]))
> + {
> + range[0] = pc_begin;
> + range[1] = pc_end;
> + }
> + else
> + {
> + if (pc_begin < range[0])
> + range[0] = pc_begin;
> + if (pc_end > range[1])
> + range[1] = pc_end;
> + }
> + }
> + else
> + {
> + if ((void *) pc_begin < ob->pc_begin)
> + ob->pc_begin = (void *) pc_begin;
> + }
> }
>
> return count;
> @@ -769,7 +836,7 @@ init_object (struct object* ob)
> fde **p = ob->u.array;
> for (count = 0; *p; ++p)
> {
> - size_t cur_count = classify_object_over_fdes (ob, *p);
> + size_t cur_count = classify_object_over_fdes (ob, *p, NULL);
> if (cur_count == (size_t) -1)
> goto unhandled_fdes;
> count += cur_count;
> @@ -777,7 +844,7 @@ init_object (struct object* ob)
> }
> else
> {
> - count = classify_object_over_fdes (ob, ob->u.single);
> + count = classify_object_over_fdes (ob, ob->u.single, NULL);
> if (count == (size_t) -1)
> {
> static const fde terminator;
> @@ -821,6 +888,32 @@ init_object (struct object* ob)
> ob->s.b.sorted = 1;
> }
>
> +#ifdef ATOMIC_FDE_FAST_PATH
> +/* Get the PC range for lookup */
> +static void
> +get_pc_range (const struct object *ob, uintptr_t *range)
> +{
> + // It is safe to cast to non-const object* here as
> + // classify_object_over_fdes does not modify ob in query mode.
> + struct object *ncob = (struct object *) (uintptr_t) ob;
> + range[0] = range[1] = 0;
> + if (ob->s.b.sorted)
> + {
> + classify_object_over_fdes (ncob, ob->u.sort->orig_data, range);
> + }
> + else if (ob->s.b.from_array)
> + {
> + fde **p = ob->u.array;
> + for (; *p; ++p)
> + classify_object_over_fdes (ncob, *p, range);
> + }
> + else
> + {
> + classify_object_over_fdes (ncob, ob->u.single, range);
> + }
> +}
> +#endif
> +
> /* A linear search through a set of FDEs for the given PC. This is
> used when there was insufficient memory to allocate and sort an
> array. */
> @@ -985,6 +1078,9 @@ binary_search_mixed_encoding_fdes (struct object
> *ob, void *pc)
> static const fde *
> search_object (struct object* ob, void *pc)
> {
> + /* The fast path initializes objects eagerly to avoid locking.
> + * On the slow path we initialize them now */
> +#ifndef ATOMIC_FDE_FAST_PATH
> /* If the data hasn't been sorted, try to do this now. We may have
> more memory available than last time we tried. */
> if (! ob->s.b.sorted)
> @@ -997,6 +1093,7 @@ search_object (struct object* ob, void *pc)
> if (pc < ob->pc_begin)
> return NULL;
> }
> +#endif
>
> if (ob->s.b.sorted)
> {
> @@ -1033,17 +1130,12 @@ _Unwind_Find_FDE (void *pc, struct
> dwarf_eh_bases *bases)
> const fde *f = NULL;
>
> #ifdef ATOMIC_FDE_FAST_PATH
> - /* For targets where unwind info is usually not registered through these
> - APIs anymore, avoid taking a global lock.
> - 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 (!__atomic_load_n (&any_objects_registered,
> - __ATOMIC_RELAXED), 1))
> + ob = btree_lookup (®istered_frames, (uintptr_t) pc);
> + if (!ob)
> return NULL;
> -#endif
> +
> + f = search_object (ob, pc);
> +#else
>
> init_object_mutex_once ();
> __gthread_mutex_lock (&object_mutex);
> @@ -1081,6 +1173,7 @@ _Unwind_Find_FDE (void *pc, struct dwarf_eh_bases
> *bases)
>
> fini:
> __gthread_mutex_unlock (&object_mutex);
> +#endif
>
> if (f)
> {
> diff --git a/libgcc/unwind-dw2-fde.h b/libgcc/unwind-dw2-fde.h
> index 8a011c358b4..77c2caa4f5a 100644
> --- a/libgcc/unwind-dw2-fde.h
> +++ b/libgcc/unwind-dw2-fde.h
> @@ -166,7 +166,7 @@ next_fde (const fde *f)
> extern const fde * _Unwind_Find_FDE (void *, struct dwarf_eh_bases *);
>
> static inline int
> -last_fde (struct object *obj __attribute__ ((__unused__)), const fde *f)
> +last_fde (const struct object *obj __attribute__ ((__unused__)), const
> fde *f)
> {
> #ifdef DWARF2_OBJECT_END_PTR_EXTENSION
> return f == (const fde *) obj->fde_end || f->length == 0;
next prev parent reply other threads:[~2022-09-16 14:49 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-09-16 10:19 Thomas Neumann
2022-09-16 14:49 ` Jason Merrill [this message]
2022-09-18 8:59 ` Dimitar Dimitrov
2022-09-18 9:20 ` Thomas Neumann
2022-09-18 10:02 ` Thomas Neumann
2022-09-19 13:46 ` Stephan Bergmann
2022-09-19 13:55 ` Thomas Neumann
2022-09-19 14:00 ` Stephan Bergmann
2022-09-19 15:33 ` Thomas Neumann
2022-09-20 5:39 ` Stephan Bergmann
2022-11-21 11:14 ` Tamar Christina
2022-11-21 11:22 ` Thomas Neumann
2022-11-21 11:48 ` Jakub Jelinek
2022-11-21 17:13 ` H.J. Lu
2022-11-22 0:31 ` Thomas Neumann
2022-11-22 8:20 ` Florian Weimer
2022-11-22 9:12 ` Thomas Neumann
2022-12-09 17:34 ` [PATCH] initialize fde objects lazily Thomas Neumann
2022-12-15 16:11 ` Tamar Christina
2022-12-16 17:25 ` Jason Merrill
2023-05-02 14:32 ` [PATCH] release the sorted FDE array when deregistering a frame [PR109685] Thomas Neumann
2023-05-10 10:49 ` [PATCH] fix radix sort on 32bit platforms [PR109670] Thomas Neumann
2023-08-10 11:33 ` [PATCH] preserve base pointer for __deregister_frame [PR110956] Thomas Neumann
2023-08-11 15:21 ` Jeff Law
2024-03-15 10:29 ` [PATCH] handle unwind tables that are embedded within unwinding code, [PR111731] Thomas Neumann
2024-03-20 8:25 ` Richard Biener
2024-03-22 13:35 ` Jeff Law
2024-03-22 13:36 ` Jeff Law
2024-03-22 14:43 ` Thomas Neumann
2022-11-22 8:00 ` [PATCH] speed up end_fde_sort using radix sort Thomas Neumann
2022-12-16 18:02 ` Jason Merrill
2022-11-21 11:49 ` [PATCH v4] eliminate mutex in fast path of __register_frame Tamar Christina
2022-11-21 11:53 ` Thomas Neumann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0f94cd2c-53e3-df07-ecaf-091266731639@redhat.com \
--to=jason@redhat.com \
--cc=fweimer@redhat.com \
--cc=gcc-patches@gcc.gnu.org \
--cc=jakub@redhat.com \
--cc=jwakely.gcc@gmail.com \
--cc=thomas.neumann@in.tum.de \
--cc=trodgers@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).