Skip to content

Commit 4edb299

Browse files
habermancopybara-github
authored andcommitted
Slightly refactored and expanded comments for arena list fusing.
This mostly just separates code out into separate functions and adds comments, but there is one small simplification in `_upb_Arena_LinkForward()` that represents a true change (though it should have identical behavior). PiperOrigin-RevId: 743607808
1 parent 5e341e0 commit 4edb299

File tree

1 file changed

+88
-31
lines changed

1 file changed

+88
-31
lines changed

upb/mem/arena.c

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,13 @@ typedef struct upb_ArenaInternal {
5757
// == NULL at end of list.
5858
UPB_ATOMIC(struct upb_ArenaInternal*) next;
5959

60-
// If the low bit is set, is a pointer to the tail of the list (populated for
61-
// roots, set to self for roots with no fused arenas). If the low bit is not
62-
// set, is a pointer to the previous node in the list, such that
63-
// a->previous_or_tail->next == a.
60+
// - If the low bit is set, is a pointer to the tail of the list (populated
61+
// for roots, set to self for roots with no fused arenas). This is best
62+
// effort, and it may not always reflect the true tail, but it will always
63+
// be a valid node in the list. This is useful for finding the list tail
64+
// without having to walk the entire list.
65+
// - If the low bit is not set, is a pointer to the previous node in the list,
66+
// such that a->previous_or_tail->next == a.
6467
UPB_ATOMIC(uintptr_t) previous_or_tail;
6568

6669
// Linked list of blocks to free/cleanup.
@@ -533,62 +536,116 @@ void upb_Arena_Free(upb_Arena* a) {
533536
goto retry;
534537
}
535538

536-
static void _upb_Arena_DoFuseArenaLists(upb_ArenaInternal* const parent,
537-
upb_ArenaInternal* child) {
539+
// Logically performs the following operation, in a way that is safe against
540+
// racing fuses:
541+
// ret = TAIL(parent)
542+
// ret->next = child
543+
// return ret
544+
//
545+
// The caller is therefore guaranteed that ret->next == child.
546+
static upb_ArenaInternal* _upb_Arena_LinkForward(
547+
upb_ArenaInternal* const parent, upb_ArenaInternal* child) {
538548
UPB_TSAN_CHECK_PUBLISHED(parent);
539549
uintptr_t parent_previous_or_tail =
540550
upb_Atomic_Load(&parent->previous_or_tail, memory_order_acquire);
541-
upb_ArenaInternal* parent_tail = parent;
542-
if (_upb_Arena_IsTaggedTail(parent_previous_or_tail)) {
543-
// Our tail might be stale, but it will always converge to the true tail.
544-
parent_tail = _upb_Arena_TailFromTagged(parent_previous_or_tail);
545-
}
546551

547-
// Link parent to child going forwards
548-
while (true) {
549-
UPB_TSAN_CHECK_PUBLISHED(parent_tail);
550-
upb_ArenaInternal* parent_tail_next =
551-
upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
552+
// Optimization: use parent->previous_or_tail to skip to TAIL(parent) in O(1)
553+
// time when possible. This is the common case because we just fused into
554+
// parent, suggesting that it should be a root with a cached tail.
555+
//
556+
// However, if there was a racing fuse, parent may no longer be a root, in
557+
// which case we need to walk the entire list to find the tail. The tail
558+
// pointer is also not guaranteed to be the true tail, so even when the
559+
// optimization is taken, we still need to walk list nodes to find the true
560+
// tail.
561+
upb_ArenaInternal* parent_tail =
562+
_upb_Arena_IsTaggedTail(parent_previous_or_tail)
563+
? _upb_Arena_TailFromTagged(parent_previous_or_tail)
564+
: parent;
565+
566+
UPB_TSAN_CHECK_PUBLISHED(parent_tail);
567+
upb_ArenaInternal* parent_tail_next =
568+
upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
569+
570+
do {
571+
// Walk the list to find the true tail (a node with next == NULL).
552572
while (parent_tail_next != NULL) {
553573
parent_tail = parent_tail_next;
554574
UPB_TSAN_CHECK_PUBLISHED(parent_tail);
555575
parent_tail_next =
556576
upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
557577
}
558-
if (upb_Atomic_CompareExchangeWeak(&parent_tail->next, &parent_tail_next,
559-
child, memory_order_release,
560-
memory_order_acquire)) {
561-
break;
562-
}
563-
if (parent_tail_next != NULL) {
564-
parent_tail = parent_tail_next;
565-
}
566-
}
578+
} while (!upb_Atomic_CompareExchangeWeak( // Replace a NULL next with child.
579+
&parent_tail->next, &parent_tail_next, child, memory_order_release,
580+
memory_order_acquire));
567581

568-
// Update parent's tail (may be stale).
582+
return parent_tail;
583+
}
584+
585+
// Updates parent->previous_or_tail = child->previous_or_tail in hopes that the
586+
// latter represents the true tail of the newly-combined list.
587+
//
588+
// This is a best-effort operation that may set the tail to a stale value, and
589+
// may fail to update the tail at all.
590+
void _upb_Arena_UpdateParentTail(upb_ArenaInternal* parent,
591+
upb_ArenaInternal* child) {
592+
// We are guaranteed that child->previous_or_tail is tagged, because we have
593+
// just transitioned child from root -> non-root, which is an exclusive
594+
// operation that can only happen once. So we are the exclusive updater of
595+
// child->previous_or_tail that can transition it from tagged to untagged.
596+
//
597+
// However, we are not guaranteed that child->previous_or_tail is the true
598+
// tail. A racing fuse may have appended to child's list but not yet updated
599+
// child->previous_or_tail.
569600
uintptr_t child_previous_or_tail =
570601
upb_Atomic_Load(&child->previous_or_tail, memory_order_acquire);
571602
upb_ArenaInternal* new_parent_tail =
572603
_upb_Arena_TailFromTagged(child_previous_or_tail);
573604
UPB_TSAN_CHECK_PUBLISHED(new_parent_tail);
574605

575-
// If another thread fused with us, don't overwrite their previous pointer
576-
// with our tail. Relaxed order is fine here as we only inspect the tag bit
577-
parent_previous_or_tail =
606+
// If another thread fused with parent, such that it is no longer a root,
607+
// don't overwrite their previous pointer with our tail. Relaxed order is fine
608+
// here as we only inspect the tag bit.
609+
uintptr_t parent_previous_or_tail =
578610
upb_Atomic_Load(&parent->previous_or_tail, memory_order_relaxed);
579611
if (_upb_Arena_IsTaggedTail(parent_previous_or_tail)) {
580612
upb_Atomic_CompareExchangeStrong(
581613
&parent->previous_or_tail, &parent_previous_or_tail,
582614
_upb_Arena_TaggedFromTail(new_parent_tail), memory_order_release,
583615
memory_order_relaxed);
584616
}
617+
}
585618

586-
// Link child to parent going backwards, for SpaceAllocated
619+
static void _upb_Arena_LinkBackward(upb_ArenaInternal* child,
620+
upb_ArenaInternal* old_parent_tail) {
621+
// Link child to parent going backwards, for SpaceAllocated. This transitions
622+
// child->previous_or_tail from tail (tagged) to previous (untagged), after
623+
// which its value is immutable.
624+
//
625+
// - We are guaranteed that no other threads are also attempting to perform
626+
// this transition (tail -> previous), because we just updated
627+
// old_parent_tail->next from NULL to non-NULL, an exclusive operation that
628+
// can only happen once.
629+
//
630+
// - _upb_Arena_UpdateParentTail() uses CAS to ensure that it
631+
// does not perform the reverse transition (previous -> tail).
632+
//
633+
// - We are guaranteed that old_parent_tail is the correct "previous" pointer,
634+
// even in the presence of racing fuses that are adding more nodes to the
635+
// list, because _upb_Arena_LinkForward() guarantees that:
636+
// old_parent_tail->next == child.
587637
upb_Atomic_Store(&child->previous_or_tail,
588-
_upb_Arena_TaggedFromPrevious(parent_tail),
638+
_upb_Arena_TaggedFromPrevious(old_parent_tail),
589639
memory_order_release);
590640
}
591641

642+
static void _upb_Arena_DoFuseArenaLists(upb_ArenaInternal* const parent,
643+
upb_ArenaInternal* child) {
644+
upb_ArenaInternal* old_parent_tail = _upb_Arena_LinkForward(parent, child);
645+
_upb_Arena_UpdateParentTail(parent, child);
646+
_upb_Arena_LinkBackward(child, old_parent_tail);
647+
}
648+
592649
void upb_Arena_SetAllocCleanup(upb_Arena* a, upb_AllocCleanupFunc* func) {
593650
UPB_TSAN_CHECK_READ(a->UPB_ONLYBITS(ptr));
594651
upb_ArenaInternal* ai = upb_Arena_Internal(a);

0 commit comments

Comments
 (0)