• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2023 Google LLC.  All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 
8 #include "upb/mem/arena.h"
9 
10 #ifdef UPB_TRACING_ENABLED
11 #include <stdatomic.h>
12 #endif
13 
14 #include <stddef.h>
15 #include <stdint.h>
16 
17 #include "upb/mem/alloc.h"
18 #include "upb/mem/internal/arena.h"
19 #include "upb/port/atomic.h"
20 
21 // Must be last.
22 #include "upb/port/def.inc"
23 
24 static UPB_ATOMIC(size_t) max_block_size = 32 << 10;
25 
upb_Arena_SetMaxBlockSize(size_t max)26 void upb_Arena_SetMaxBlockSize(size_t max) { max_block_size = max; }
27 
28 typedef struct upb_MemBlock {
29   // Atomic only for the benefit of SpaceAllocated().
30   UPB_ATOMIC(struct upb_MemBlock*) next;
31   uint32_t size;
32   // Data follows.
33 } upb_MemBlock;
34 
35 typedef struct upb_ArenaInternal {
36   // upb_alloc* together with a low bit which signals if there is an initial
37   // block.
38   uintptr_t block_alloc;
39 
40   // When multiple arenas are fused together, each arena points to a parent
41   // arena (root points to itself). The root tracks how many live arenas
42   // reference it.
43 
44   // The low bit is tagged:
45   //   0: pointer to parent
46   //   1: count, left shifted by one
47   UPB_ATOMIC(uintptr_t) parent_or_count;
48 
49   // All nodes that are fused together are in a singly-linked list.
50   // == NULL at end of list.
51   UPB_ATOMIC(struct upb_ArenaInternal*) next;
52 
53   // The last element of the linked list. This is present only as an
54   // optimization, so that we do not have to iterate over all members for every
55   // fuse.  Only significant for an arena root. In other cases it is ignored.
56   // == self when no other list members.
57   UPB_ATOMIC(struct upb_ArenaInternal*) tail;
58 
59   // Linked list of blocks to free/cleanup. Atomic only for the benefit of
60   // upb_Arena_SpaceAllocated().
61   UPB_ATOMIC(upb_MemBlock*) blocks;
62 } upb_ArenaInternal;
63 
64 // All public + private state for an arena.
65 typedef struct {
66   upb_Arena head;
67   upb_ArenaInternal body;
68 } upb_ArenaState;
69 
70 typedef struct {
71   upb_ArenaInternal* root;
72   uintptr_t tagged_count;
73 } upb_ArenaRoot;
74 
75 static const size_t kUpb_MemblockReserve =
76     UPB_ALIGN_UP(sizeof(upb_MemBlock), UPB_MALLOC_ALIGN);
77 
78 // Extracts the (upb_ArenaInternal*) from a (upb_Arena*)
upb_Arena_Internal(const upb_Arena * a)79 static upb_ArenaInternal* upb_Arena_Internal(const upb_Arena* a) {
80   return &((upb_ArenaState*)a)->body;
81 }
82 
_upb_Arena_IsTaggedRefcount(uintptr_t parent_or_count)83 static bool _upb_Arena_IsTaggedRefcount(uintptr_t parent_or_count) {
84   return (parent_or_count & 1) == 1;
85 }
86 
_upb_Arena_IsTaggedPointer(uintptr_t parent_or_count)87 static bool _upb_Arena_IsTaggedPointer(uintptr_t parent_or_count) {
88   return (parent_or_count & 1) == 0;
89 }
90 
_upb_Arena_RefCountFromTagged(uintptr_t parent_or_count)91 static uintptr_t _upb_Arena_RefCountFromTagged(uintptr_t parent_or_count) {
92   UPB_ASSERT(_upb_Arena_IsTaggedRefcount(parent_or_count));
93   return parent_or_count >> 1;
94 }
95 
_upb_Arena_TaggedFromRefcount(uintptr_t refcount)96 static uintptr_t _upb_Arena_TaggedFromRefcount(uintptr_t refcount) {
97   uintptr_t parent_or_count = (refcount << 1) | 1;
98   UPB_ASSERT(_upb_Arena_IsTaggedRefcount(parent_or_count));
99   return parent_or_count;
100 }
101 
_upb_Arena_PointerFromTagged(uintptr_t parent_or_count)102 static upb_ArenaInternal* _upb_Arena_PointerFromTagged(
103     uintptr_t parent_or_count) {
104   UPB_ASSERT(_upb_Arena_IsTaggedPointer(parent_or_count));
105   return (upb_ArenaInternal*)parent_or_count;
106 }
107 
_upb_Arena_TaggedFromPointer(upb_ArenaInternal * ai)108 static uintptr_t _upb_Arena_TaggedFromPointer(upb_ArenaInternal* ai) {
109   uintptr_t parent_or_count = (uintptr_t)ai;
110   UPB_ASSERT(_upb_Arena_IsTaggedPointer(parent_or_count));
111   return parent_or_count;
112 }
113 
_upb_ArenaInternal_BlockAlloc(upb_ArenaInternal * ai)114 static upb_alloc* _upb_ArenaInternal_BlockAlloc(upb_ArenaInternal* ai) {
115   return (upb_alloc*)(ai->block_alloc & ~0x1);
116 }
117 
_upb_Arena_MakeBlockAlloc(upb_alloc * alloc,bool has_initial)118 static uintptr_t _upb_Arena_MakeBlockAlloc(upb_alloc* alloc, bool has_initial) {
119   uintptr_t alloc_uint = (uintptr_t)alloc;
120   UPB_ASSERT((alloc_uint & 1) == 0);
121   return alloc_uint | (has_initial ? 1 : 0);
122 }
123 
_upb_ArenaInternal_HasInitialBlock(upb_ArenaInternal * ai)124 static bool _upb_ArenaInternal_HasInitialBlock(upb_ArenaInternal* ai) {
125   return ai->block_alloc & 0x1;
126 }
127 
128 #ifdef UPB_TRACING_ENABLED
129 static void (*_init_arena_trace_handler)(const upb_Arena*, size_t size) = NULL;
130 static void (*_fuse_arena_trace_handler)(const upb_Arena*,
131                                          const upb_Arena*) = NULL;
132 static void (*_free_arena_trace_handler)(const upb_Arena*) = NULL;
133 
upb_Arena_SetTraceHandler(void (* initArenaTraceHandler)(const upb_Arena *,size_t size),void (* fuseArenaTraceHandler)(const upb_Arena *,const upb_Arena *),void (* freeArenaTraceHandler)(const upb_Arena *))134 void upb_Arena_SetTraceHandler(
135     void (*initArenaTraceHandler)(const upb_Arena*, size_t size),
136     void (*fuseArenaTraceHandler)(const upb_Arena*, const upb_Arena*),
137     void (*freeArenaTraceHandler)(const upb_Arena*)) {
138   _init_arena_trace_handler = initArenaTraceHandler;
139   _fuse_arena_trace_handler = fuseArenaTraceHandler;
140   _free_arena_trace_handler = freeArenaTraceHandler;
141 }
142 
upb_Arena_LogInit(const upb_Arena * arena,size_t size)143 void upb_Arena_LogInit(const upb_Arena* arena, size_t size) {
144   if (_init_arena_trace_handler) {
145     _init_arena_trace_handler(arena, size);
146   }
147 }
upb_Arena_LogFuse(const upb_Arena * arena1,const upb_Arena * arena2)148 void upb_Arena_LogFuse(const upb_Arena* arena1, const upb_Arena* arena2) {
149   if (_fuse_arena_trace_handler) {
150     _fuse_arena_trace_handler(arena1, arena2);
151   }
152 }
upb_Arena_LogFree(const upb_Arena * arena)153 void upb_Arena_LogFree(const upb_Arena* arena) {
154   if (_free_arena_trace_handler) {
155     _free_arena_trace_handler(arena);
156   }
157 }
158 #endif  // UPB_TRACING_ENABLED
159 
_upb_Arena_FindRoot(upb_Arena * a)160 static upb_ArenaRoot _upb_Arena_FindRoot(upb_Arena* a) {
161   upb_ArenaInternal* ai = upb_Arena_Internal(a);
162   uintptr_t poc = upb_Atomic_Load(&ai->parent_or_count, memory_order_acquire);
163   while (_upb_Arena_IsTaggedPointer(poc)) {
164     upb_ArenaInternal* next = _upb_Arena_PointerFromTagged(poc);
165     UPB_ASSERT(ai != next);
166     uintptr_t next_poc =
167         upb_Atomic_Load(&next->parent_or_count, memory_order_acquire);
168 
169     if (_upb_Arena_IsTaggedPointer(next_poc)) {
170       // To keep complexity down, we lazily collapse levels of the tree.  This
171       // keeps it flat in the final case, but doesn't cost much incrementally.
172       //
173       // Path splitting keeps time complexity down, see:
174       //   https://en.wikipedia.org/wiki/Disjoint-set_data_structure
175       //
176       // We can safely use a relaxed atomic here because all threads doing this
177       // will converge on the same value and we don't need memory orderings to
178       // be visible.
179       //
180       // This is true because:
181       // - If no fuses occur, this will eventually become the root.
182       // - If fuses are actively occurring, the root may change, but the
183       //   invariant is that `parent_or_count` merely points to *a* parent.
184       //
185       // In other words, it is moving towards "the" root, and that root may move
186       // further away over time, but the path towards that root will continue to
187       // be valid and the creation of the path carries all the memory orderings
188       // required.
189       UPB_ASSERT(ai != _upb_Arena_PointerFromTagged(next_poc));
190       upb_Atomic_Store(&ai->parent_or_count, next_poc, memory_order_relaxed);
191     }
192     ai = next;
193     poc = next_poc;
194   }
195   return (upb_ArenaRoot){.root = ai, .tagged_count = poc};
196 }
197 
upb_Arena_SpaceAllocated(upb_Arena * arena,size_t * fused_count)198 size_t upb_Arena_SpaceAllocated(upb_Arena* arena, size_t* fused_count) {
199   upb_ArenaInternal* ai = _upb_Arena_FindRoot(arena).root;
200   size_t memsize = 0;
201   size_t local_fused_count = 0;
202 
203   while (ai != NULL) {
204     upb_MemBlock* block = upb_Atomic_Load(&ai->blocks, memory_order_relaxed);
205     while (block != NULL) {
206       memsize += sizeof(upb_MemBlock) + block->size;
207       block = upb_Atomic_Load(&block->next, memory_order_relaxed);
208     }
209     ai = upb_Atomic_Load(&ai->next, memory_order_relaxed);
210     local_fused_count++;
211   }
212 
213   if (fused_count) *fused_count = local_fused_count;
214   return memsize;
215 }
216 
UPB_PRIVATE(_upb_Arena_Contains)217 bool UPB_PRIVATE(_upb_Arena_Contains)(const upb_Arena* a, void* ptr) {
218   upb_ArenaInternal* ai = upb_Arena_Internal(a);
219   UPB_ASSERT(ai);
220 
221   upb_MemBlock* block = upb_Atomic_Load(&ai->blocks, memory_order_relaxed);
222   while (block) {
223     uintptr_t beg = (uintptr_t)block;
224     uintptr_t end = beg + block->size;
225     if ((uintptr_t)ptr >= beg && (uintptr_t)ptr < end) return true;
226     block = upb_Atomic_Load(&block->next, memory_order_relaxed);
227   }
228 
229   return false;
230 }
231 
upb_Arena_DebugRefCount(upb_Arena * a)232 uint32_t upb_Arena_DebugRefCount(upb_Arena* a) {
233   upb_ArenaInternal* ai = upb_Arena_Internal(a);
234   // These loads could probably be relaxed, but given that this is debug-only,
235   // it's not worth introducing a new variant for it.
236   uintptr_t poc = upb_Atomic_Load(&ai->parent_or_count, memory_order_acquire);
237   while (_upb_Arena_IsTaggedPointer(poc)) {
238     ai = _upb_Arena_PointerFromTagged(poc);
239     poc = upb_Atomic_Load(&ai->parent_or_count, memory_order_acquire);
240   }
241   return _upb_Arena_RefCountFromTagged(poc);
242 }
243 
_upb_Arena_AddBlock(upb_Arena * a,void * ptr,size_t size)244 static void _upb_Arena_AddBlock(upb_Arena* a, void* ptr, size_t size) {
245   upb_ArenaInternal* ai = upb_Arena_Internal(a);
246   upb_MemBlock* block = ptr;
247 
248   // Insert into linked list.
249   block->size = (uint32_t)size;
250   upb_Atomic_Init(&block->next, ai->blocks);
251   upb_Atomic_Store(&ai->blocks, block, memory_order_release);
252 
253   a->UPB_PRIVATE(ptr) = UPB_PTR_AT(block, kUpb_MemblockReserve, char);
254   a->UPB_PRIVATE(end) = UPB_PTR_AT(block, size, char);
255 
256   UPB_POISON_MEMORY_REGION(a->UPB_PRIVATE(ptr),
257                            a->UPB_PRIVATE(end) - a->UPB_PRIVATE(ptr));
258 }
259 
_upb_Arena_AllocBlock(upb_Arena * a,size_t size)260 static bool _upb_Arena_AllocBlock(upb_Arena* a, size_t size) {
261   upb_ArenaInternal* ai = upb_Arena_Internal(a);
262   if (!ai->block_alloc) return false;
263   upb_MemBlock* last_block = upb_Atomic_Load(&ai->blocks, memory_order_acquire);
264   size_t last_size = last_block != NULL ? last_block->size : 128;
265 
266   // Don't naturally grow beyond the max block size.
267   size_t clamped_size = UPB_MIN(last_size * 2, max_block_size);
268 
269   // We may need to exceed the max block size if the user requested a large
270   // allocation.
271   size_t block_size = UPB_MAX(size, clamped_size) + kUpb_MemblockReserve;
272 
273   upb_MemBlock* block =
274       upb_malloc(_upb_ArenaInternal_BlockAlloc(ai), block_size);
275 
276   if (!block) return false;
277   _upb_Arena_AddBlock(a, block, block_size);
278   UPB_ASSERT(UPB_PRIVATE(_upb_ArenaHas)(a) >= size);
279   return true;
280 }
281 
UPB_PRIVATE(_upb_Arena_SlowMalloc)282 void* UPB_PRIVATE(_upb_Arena_SlowMalloc)(upb_Arena* a, size_t size) {
283   if (!_upb_Arena_AllocBlock(a, size)) return NULL;  // OOM
284   return upb_Arena_Malloc(a, size - UPB_ASAN_GUARD_SIZE);
285 }
286 
_upb_Arena_InitSlow(upb_alloc * alloc)287 static upb_Arena* _upb_Arena_InitSlow(upb_alloc* alloc) {
288   const size_t first_block_overhead =
289       sizeof(upb_ArenaState) + kUpb_MemblockReserve;
290   upb_ArenaState* a;
291 
292   // We need to malloc the initial block.
293   char* mem;
294   size_t n = first_block_overhead + 256;
295   if (!alloc || !(mem = upb_malloc(alloc, n))) {
296     return NULL;
297   }
298 
299   a = UPB_PTR_AT(mem, n - sizeof(upb_ArenaState), upb_ArenaState);
300   n -= sizeof(upb_ArenaState);
301 
302   a->body.block_alloc = _upb_Arena_MakeBlockAlloc(alloc, 0);
303   upb_Atomic_Init(&a->body.parent_or_count, _upb_Arena_TaggedFromRefcount(1));
304   upb_Atomic_Init(&a->body.next, NULL);
305   upb_Atomic_Init(&a->body.tail, &a->body);
306   upb_Atomic_Init(&a->body.blocks, NULL);
307 
308   _upb_Arena_AddBlock(&a->head, mem, n);
309 
310   return &a->head;
311 }
312 
upb_Arena_Init(void * mem,size_t n,upb_alloc * alloc)313 upb_Arena* upb_Arena_Init(void* mem, size_t n, upb_alloc* alloc) {
314   UPB_ASSERT(sizeof(void*) * UPB_ARENA_SIZE_HACK >= sizeof(upb_ArenaState));
315   upb_ArenaState* a;
316 
317   if (n) {
318     /* Align initial pointer up so that we return properly-aligned pointers. */
319     void* aligned = (void*)UPB_ALIGN_UP((uintptr_t)mem, UPB_MALLOC_ALIGN);
320     size_t delta = (uintptr_t)aligned - (uintptr_t)mem;
321     n = delta <= n ? n - delta : 0;
322     mem = aligned;
323   }
324 
325   /* Round block size down to alignof(*a) since we will allocate the arena
326    * itself at the end. */
327   n = UPB_ALIGN_DOWN(n, UPB_ALIGN_OF(upb_ArenaState));
328 
329   if (UPB_UNLIKELY(n < sizeof(upb_ArenaState))) {
330 #ifdef UPB_TRACING_ENABLED
331     upb_Arena* ret = _upb_Arena_InitSlow(alloc);
332     upb_Arena_LogInit(ret, n);
333     return ret;
334 #else
335     return _upb_Arena_InitSlow(alloc);
336 #endif
337   }
338 
339   a = UPB_PTR_AT(mem, n - sizeof(upb_ArenaState), upb_ArenaState);
340 
341   upb_Atomic_Init(&a->body.parent_or_count, _upb_Arena_TaggedFromRefcount(1));
342   upb_Atomic_Init(&a->body.next, NULL);
343   upb_Atomic_Init(&a->body.tail, &a->body);
344   upb_Atomic_Init(&a->body.blocks, NULL);
345 
346   a->body.block_alloc = _upb_Arena_MakeBlockAlloc(alloc, 1);
347   a->head.UPB_PRIVATE(ptr) = mem;
348   a->head.UPB_PRIVATE(end) = UPB_PTR_AT(mem, n - sizeof(upb_ArenaState), char);
349 #ifdef UPB_TRACING_ENABLED
350   upb_Arena_LogInit(&a->head, n);
351 #endif
352   return &a->head;
353 }
354 
_upb_Arena_DoFree(upb_ArenaInternal * ai)355 static void _upb_Arena_DoFree(upb_ArenaInternal* ai) {
356   UPB_ASSERT(_upb_Arena_RefCountFromTagged(ai->parent_or_count) == 1);
357   while (ai != NULL) {
358     // Load first since arena itself is likely from one of its blocks.
359     upb_ArenaInternal* next_arena =
360         (upb_ArenaInternal*)upb_Atomic_Load(&ai->next, memory_order_acquire);
361     upb_alloc* block_alloc = _upb_ArenaInternal_BlockAlloc(ai);
362     upb_MemBlock* block = upb_Atomic_Load(&ai->blocks, memory_order_acquire);
363     while (block != NULL) {
364       // Load first since we are deleting block.
365       upb_MemBlock* next_block =
366           upb_Atomic_Load(&block->next, memory_order_acquire);
367       upb_free(block_alloc, block);
368       block = next_block;
369     }
370     ai = next_arena;
371   }
372 }
373 
upb_Arena_Free(upb_Arena * a)374 void upb_Arena_Free(upb_Arena* a) {
375   upb_ArenaInternal* ai = upb_Arena_Internal(a);
376   uintptr_t poc = upb_Atomic_Load(&ai->parent_or_count, memory_order_acquire);
377 retry:
378   while (_upb_Arena_IsTaggedPointer(poc)) {
379     ai = _upb_Arena_PointerFromTagged(poc);
380     poc = upb_Atomic_Load(&ai->parent_or_count, memory_order_acquire);
381   }
382 
383   // compare_exchange or fetch_sub are RMW operations, which are more
384   // expensive then direct loads.  As an optimization, we only do RMW ops
385   // when we need to update things for other threads to see.
386   if (poc == _upb_Arena_TaggedFromRefcount(1)) {
387 #ifdef UPB_TRACING_ENABLED
388     upb_Arena_LogFree(a);
389 #endif
390     _upb_Arena_DoFree(ai);
391     return;
392   }
393 
394   if (upb_Atomic_CompareExchangeWeak(
395           &ai->parent_or_count, &poc,
396           _upb_Arena_TaggedFromRefcount(_upb_Arena_RefCountFromTagged(poc) - 1),
397           memory_order_release, memory_order_acquire)) {
398     // We were >1 and we decremented it successfully, so we are done.
399     return;
400   }
401 
402   // We failed our update, so someone has done something, retry the whole
403   // process, but the failed exchange reloaded `poc` for us.
404   goto retry;
405 }
406 
_upb_Arena_DoFuseArenaLists(upb_ArenaInternal * const parent,upb_ArenaInternal * child)407 static void _upb_Arena_DoFuseArenaLists(upb_ArenaInternal* const parent,
408                                         upb_ArenaInternal* child) {
409   upb_ArenaInternal* parent_tail =
410       upb_Atomic_Load(&parent->tail, memory_order_relaxed);
411 
412   do {
413     // Our tail might be stale, but it will always converge to the true tail.
414     upb_ArenaInternal* parent_tail_next =
415         upb_Atomic_Load(&parent_tail->next, memory_order_relaxed);
416     while (parent_tail_next != NULL) {
417       parent_tail = parent_tail_next;
418       parent_tail_next =
419           upb_Atomic_Load(&parent_tail->next, memory_order_relaxed);
420     }
421 
422     upb_ArenaInternal* displaced =
423         upb_Atomic_Exchange(&parent_tail->next, child, memory_order_relaxed);
424     parent_tail = upb_Atomic_Load(&child->tail, memory_order_relaxed);
425 
426     // If we displaced something that got installed racily, we can simply
427     // reinstall it on our new tail.
428     child = displaced;
429   } while (child != NULL);
430 
431   upb_Atomic_Store(&parent->tail, parent_tail, memory_order_relaxed);
432 }
433 
_upb_Arena_DoFuse(upb_Arena * a1,upb_Arena * a2,uintptr_t * ref_delta)434 static upb_ArenaInternal* _upb_Arena_DoFuse(upb_Arena* a1, upb_Arena* a2,
435                                             uintptr_t* ref_delta) {
436   // `parent_or_count` has two distinct modes
437   // -  parent pointer mode
438   // -  refcount mode
439   //
440   // In parent pointer mode, it may change what pointer it refers to in the
441   // tree, but it will always approach a root.  Any operation that walks the
442   // tree to the root may collapse levels of the tree concurrently.
443   upb_ArenaRoot r1 = _upb_Arena_FindRoot(a1);
444   upb_ArenaRoot r2 = _upb_Arena_FindRoot(a2);
445 
446   if (r1.root == r2.root) return r1.root;  // Already fused.
447 
448   // Avoid cycles by always fusing into the root with the lower address.
449   if ((uintptr_t)r1.root > (uintptr_t)r2.root) {
450     upb_ArenaRoot tmp = r1;
451     r1 = r2;
452     r2 = tmp;
453   }
454 
455   // The moment we install `r1` as the parent for `r2` all racing frees may
456   // immediately begin decrementing `r1`'s refcount (including pending
457   // increments to that refcount and their frees!).  We need to add `r2`'s refs
458   // now, so that `r1` can withstand any unrefs that come from r2.
459   //
460   // Note that while it is possible for `r2`'s refcount to increase
461   // asynchronously, we will not actually do the reparenting operation below
462   // unless `r2`'s refcount is unchanged from when we read it.
463   //
464   // Note that we may have done this previously, either to this node or a
465   // different node, during a previous and failed DoFuse() attempt. But we will
466   // not lose track of these refs because we always add them to our overall
467   // delta.
468   uintptr_t r2_untagged_count = r2.tagged_count & ~1;
469   uintptr_t with_r2_refs = r1.tagged_count + r2_untagged_count;
470   if (!upb_Atomic_CompareExchangeStrong(
471           &r1.root->parent_or_count, &r1.tagged_count, with_r2_refs,
472           memory_order_release, memory_order_acquire)) {
473     return NULL;
474   }
475 
476   // Perform the actual fuse by removing the refs from `r2` and swapping in the
477   // parent pointer.
478   if (!upb_Atomic_CompareExchangeStrong(
479           &r2.root->parent_or_count, &r2.tagged_count,
480           _upb_Arena_TaggedFromPointer(r1.root), memory_order_release,
481           memory_order_acquire)) {
482     // We'll need to remove the excess refs we added to r1 previously.
483     *ref_delta += r2_untagged_count;
484     return NULL;
485   }
486 
487   // Now that the fuse has been performed (and can no longer fail) we need to
488   // append `r2` to `r1`'s linked list.
489   _upb_Arena_DoFuseArenaLists(r1.root, r2.root);
490   return r1.root;
491 }
492 
_upb_Arena_FixupRefs(upb_ArenaInternal * new_root,uintptr_t ref_delta)493 static bool _upb_Arena_FixupRefs(upb_ArenaInternal* new_root,
494                                  uintptr_t ref_delta) {
495   if (ref_delta == 0) return true;  // No fixup required.
496   uintptr_t poc =
497       upb_Atomic_Load(&new_root->parent_or_count, memory_order_relaxed);
498   if (_upb_Arena_IsTaggedPointer(poc)) return false;
499   uintptr_t with_refs = poc - ref_delta;
500   UPB_ASSERT(!_upb_Arena_IsTaggedPointer(with_refs));
501   return upb_Atomic_CompareExchangeStrong(&new_root->parent_or_count, &poc,
502                                           with_refs, memory_order_relaxed,
503                                           memory_order_relaxed);
504 }
505 
upb_Arena_Fuse(upb_Arena * a1,upb_Arena * a2)506 bool upb_Arena_Fuse(upb_Arena* a1, upb_Arena* a2) {
507   if (a1 == a2) return true;  // trivial fuse
508 
509 #ifdef UPB_TRACING_ENABLED
510   upb_Arena_LogFuse(a1, a2);
511 #endif
512 
513   upb_ArenaInternal* ai1 = upb_Arena_Internal(a1);
514   upb_ArenaInternal* ai2 = upb_Arena_Internal(a2);
515 
516   // Do not fuse initial blocks since we cannot lifetime extend them.
517   // Any other fuse scenario is allowed.
518   if (_upb_ArenaInternal_HasInitialBlock(ai1) ||
519       _upb_ArenaInternal_HasInitialBlock(ai2)) {
520     return false;
521   }
522 
523   // The number of refs we ultimately need to transfer to the new root.
524   uintptr_t ref_delta = 0;
525   while (true) {
526     upb_ArenaInternal* new_root = _upb_Arena_DoFuse(a1, a2, &ref_delta);
527     if (new_root != NULL && _upb_Arena_FixupRefs(new_root, ref_delta)) {
528       return true;
529     }
530   }
531 }
532 
upb_Arena_IncRefFor(upb_Arena * a,const void * owner)533 bool upb_Arena_IncRefFor(upb_Arena* a, const void* owner) {
534   upb_ArenaInternal* ai = upb_Arena_Internal(a);
535   if (_upb_ArenaInternal_HasInitialBlock(ai)) return false;
536   upb_ArenaRoot r;
537 
538 retry:
539   r = _upb_Arena_FindRoot(a);
540   if (upb_Atomic_CompareExchangeWeak(
541           &r.root->parent_or_count, &r.tagged_count,
542           _upb_Arena_TaggedFromRefcount(
543               _upb_Arena_RefCountFromTagged(r.tagged_count) + 1),
544           memory_order_release, memory_order_acquire)) {
545     // We incremented it successfully, so we are done.
546     return true;
547   }
548   // We failed update due to parent switching on the arena.
549   goto retry;
550 }
551 
upb_Arena_DecRefFor(upb_Arena * a,const void * owner)552 void upb_Arena_DecRefFor(upb_Arena* a, const void* owner) { upb_Arena_Free(a); }
553 
UPB_PRIVATE(_upb_Arena_SwapIn)554 void UPB_PRIVATE(_upb_Arena_SwapIn)(upb_Arena* des, const upb_Arena* src) {
555   upb_ArenaInternal* desi = upb_Arena_Internal(des);
556   upb_ArenaInternal* srci = upb_Arena_Internal(src);
557 
558   *des = *src;
559   desi->block_alloc = srci->block_alloc;
560   upb_MemBlock* blocks = upb_Atomic_Load(&srci->blocks, memory_order_relaxed);
561   upb_Atomic_Init(&desi->blocks, blocks);
562 }
563 
UPB_PRIVATE(_upb_Arena_SwapOut)564 void UPB_PRIVATE(_upb_Arena_SwapOut)(upb_Arena* des, const upb_Arena* src) {
565   upb_ArenaInternal* desi = upb_Arena_Internal(des);
566   upb_ArenaInternal* srci = upb_Arena_Internal(src);
567 
568   *des = *src;
569   upb_MemBlock* blocks = upb_Atomic_Load(&srci->blocks, memory_order_relaxed);
570   upb_Atomic_Store(&desi->blocks, blocks, memory_order_relaxed);
571 }
572