@@ -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+
592649void 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