@@ -3007,10 +3007,13 @@ typedef struct upb_ArenaInternal {
30073007 // == NULL at end of list.
30083008 UPB_ATOMIC(struct upb_ArenaInternal*) next;
30093009
3010- // If the low bit is set, is a pointer to the tail of the list (populated for
3011- // roots, set to self for roots with no fused arenas). If the low bit is not
3012- // set, is a pointer to the previous node in the list, such that
3013- // a->previous_or_tail->next == a.
3010+ // - If the low bit is set, is a pointer to the tail of the list (populated
3011+ // for roots, set to self for roots with no fused arenas). This is best
3012+ // effort, and it may not always reflect the true tail, but it will always
3013+ // be a valid node in the list. This is useful for finding the list tail
3014+ // without having to walk the entire list.
3015+ // - If the low bit is not set, is a pointer to the previous node in the list,
3016+ // such that a->previous_or_tail->next == a.
30143017 UPB_ATOMIC(uintptr_t) previous_or_tail;
30153018
30163019 // Linked list of blocks to free/cleanup.
@@ -3483,62 +3486,116 @@ void upb_Arena_Free(upb_Arena* a) {
34833486 goto retry;
34843487}
34853488
3486- static void _upb_Arena_DoFuseArenaLists(upb_ArenaInternal* const parent,
3487- upb_ArenaInternal* child) {
3489+ // Logically performs the following operation, in a way that is safe against
3490+ // racing fuses:
3491+ // ret = TAIL(parent)
3492+ // ret->next = child
3493+ // return ret
3494+ //
3495+ // The caller is therefore guaranteed that ret->next == child.
3496+ static upb_ArenaInternal* _upb_Arena_LinkForward(
3497+ upb_ArenaInternal* const parent, upb_ArenaInternal* child) {
34883498 UPB_TSAN_CHECK_PUBLISHED(parent);
34893499 uintptr_t parent_previous_or_tail =
34903500 upb_Atomic_Load(&parent->previous_or_tail, memory_order_acquire);
3491- upb_ArenaInternal* parent_tail = parent;
3492- if (_upb_Arena_IsTaggedTail(parent_previous_or_tail)) {
3493- // Our tail might be stale, but it will always converge to the true tail.
3494- parent_tail = _upb_Arena_TailFromTagged(parent_previous_or_tail);
3495- }
34963501
3497- // Link parent to child going forwards
3498- while (true) {
3499- UPB_TSAN_CHECK_PUBLISHED(parent_tail);
3500- upb_ArenaInternal* parent_tail_next =
3501- upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
3502+ // Optimization: use parent->previous_or_tail to skip to TAIL(parent) in O(1)
3503+ // time when possible. This is the common case because we just fused into
3504+ // parent, suggesting that it should be a root with a cached tail.
3505+ //
3506+ // However, if there was a racing fuse, parent may no longer be a root, in
3507+ // which case we need to walk the entire list to find the tail. The tail
3508+ // pointer is also not guaranteed to be the true tail, so even when the
3509+ // optimization is taken, we still need to walk list nodes to find the true
3510+ // tail.
3511+ upb_ArenaInternal* parent_tail =
3512+ _upb_Arena_IsTaggedTail(parent_previous_or_tail)
3513+ ? _upb_Arena_TailFromTagged(parent_previous_or_tail)
3514+ : parent;
3515+
3516+ UPB_TSAN_CHECK_PUBLISHED(parent_tail);
3517+ upb_ArenaInternal* parent_tail_next =
3518+ upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
3519+
3520+ do {
3521+ // Walk the list to find the true tail (a node with next == NULL).
35023522 while (parent_tail_next != NULL) {
35033523 parent_tail = parent_tail_next;
35043524 UPB_TSAN_CHECK_PUBLISHED(parent_tail);
35053525 parent_tail_next =
35063526 upb_Atomic_Load(&parent_tail->next, memory_order_acquire);
35073527 }
3508- if (upb_Atomic_CompareExchangeWeak(&parent_tail->next, &parent_tail_next,
3509- child, memory_order_release,
3510- memory_order_acquire)) {
3511- break;
3512- }
3513- if (parent_tail_next != NULL) {
3514- parent_tail = parent_tail_next;
3515- }
3516- }
3528+ } while (!upb_Atomic_CompareExchangeWeak( // Replace a NULL next with child.
3529+ &parent_tail->next, &parent_tail_next, child, memory_order_release,
3530+ memory_order_acquire));
35173531
3518- // Update parent's tail (may be stale).
3532+ return parent_tail;
3533+ }
3534+
3535+ // Updates parent->previous_or_tail = child->previous_or_tail in hopes that the
3536+ // latter represents the true tail of the newly-combined list.
3537+ //
3538+ // This is a best-effort operation that may set the tail to a stale value, and
3539+ // may fail to update the tail at all.
3540+ void _upb_Arena_UpdateParentTail(upb_ArenaInternal* parent,
3541+ upb_ArenaInternal* child) {
3542+ // We are guaranteed that child->previous_or_tail is tagged, because we have
3543+ // just transitioned child from root -> non-root, which is an exclusive
3544+ // operation that can only happen once. So we are the exclusive updater of
3545+ // child->previous_or_tail that can transition it from tagged to untagged.
3546+ //
3547+ // However, we are not guaranteed that child->previous_or_tail is the true
3548+ // tail. A racing fuse may have appended to child's list but not yet updated
3549+ // child->previous_or_tail.
35193550 uintptr_t child_previous_or_tail =
35203551 upb_Atomic_Load(&child->previous_or_tail, memory_order_acquire);
35213552 upb_ArenaInternal* new_parent_tail =
35223553 _upb_Arena_TailFromTagged(child_previous_or_tail);
35233554 UPB_TSAN_CHECK_PUBLISHED(new_parent_tail);
35243555
3525- // If another thread fused with us, don't overwrite their previous pointer
3526- // with our tail. Relaxed order is fine here as we only inspect the tag bit
3527- parent_previous_or_tail =
3556+ // If another thread fused with parent, such that it is no longer a root,
3557+ // don't overwrite their previous pointer with our tail. Relaxed order is fine
3558+ // here as we only inspect the tag bit.
3559+ uintptr_t parent_previous_or_tail =
35283560 upb_Atomic_Load(&parent->previous_or_tail, memory_order_relaxed);
35293561 if (_upb_Arena_IsTaggedTail(parent_previous_or_tail)) {
35303562 upb_Atomic_CompareExchangeStrong(
35313563 &parent->previous_or_tail, &parent_previous_or_tail,
35323564 _upb_Arena_TaggedFromTail(new_parent_tail), memory_order_release,
35333565 memory_order_relaxed);
35343566 }
3567+ }
35353568
3536- // Link child to parent going backwards, for SpaceAllocated
3569+ static void _upb_Arena_LinkBackward(upb_ArenaInternal* child,
3570+ upb_ArenaInternal* old_parent_tail) {
3571+ // Link child to parent going backwards, for SpaceAllocated. This transitions
3572+ // child->previous_or_tail from tail (tagged) to previous (untagged), after
3573+ // which its value is immutable.
3574+ //
3575+ // - We are guaranteed that no other threads are also attempting to perform
3576+ // this transition (tail -> previous), because we just updated
3577+ // old_parent_tail->next from NULL to non-NULL, an exclusive operation that
3578+ // can only happen once.
3579+ //
3580+ // - _upb_Arena_UpdateParentTail() uses CAS to ensure that it
3581+ // does not perform the reverse transition (previous -> tail).
3582+ //
3583+ // - We are guaranteed that old_parent_tail is the correct "previous" pointer,
3584+ // even in the presence of racing fuses that are adding more nodes to the
3585+ // list, because _upb_Arena_LinkForward() guarantees that:
3586+ // old_parent_tail->next == child.
35373587 upb_Atomic_Store(&child->previous_or_tail,
3538- _upb_Arena_TaggedFromPrevious(parent_tail ),
3588+ _upb_Arena_TaggedFromPrevious(old_parent_tail ),
35393589 memory_order_release);
35403590}
35413591
3592+ static void _upb_Arena_DoFuseArenaLists(upb_ArenaInternal* const parent,
3593+ upb_ArenaInternal* child) {
3594+ upb_ArenaInternal* old_parent_tail = _upb_Arena_LinkForward(parent, child);
3595+ _upb_Arena_UpdateParentTail(parent, child);
3596+ _upb_Arena_LinkBackward(child, old_parent_tail);
3597+ }
3598+
35423599void upb_Arena_SetAllocCleanup(upb_Arena* a, upb_AllocCleanupFunc* func) {
35433600 UPB_TSAN_CHECK_READ(a->UPB_ONLYBITS(ptr));
35443601 upb_ArenaInternal* ai = upb_Arena_Internal(a);
0 commit comments