Skip to content

Commit 9551473

Browse files
Auto-generate files after cl/743607808
1 parent 4edb299 commit 9551473

File tree

2 files changed

+176
-62
lines changed

2 files changed

+176
-62
lines changed

php/ext/google/protobuf/php-upb.c

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
35423599
void 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);

ruby/ext/google/protobuf_c/ruby-upb.c

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
35423599
void 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

Comments
 (0)