| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2025 Google LLC. All rights reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file or at | |
| // https://developers.google.com/open-source/licenses/bsd | |
| #include "upb/mini_table/generated_registry.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include "upb/mem/alloc.h" | |
| #include "upb/mem/arena.h" | |
| #include "upb/mini_table/extension.h" | |
| #include "upb/mini_table/extension_registry.h" | |
| #include "upb/mini_table/internal/generated_registry.h" // IWYU pragma: keep | |
| #include "upb/port/atomic.h" | |
| // Must be last. | |
| #include "upb/port/def.inc" | |
| #if UPB_TSAN | |
| #include <sched.h> | |
| #endif // UPB_TSAN | |
| const UPB_PRIVATE(upb_GeneratedExtensionListEntry) * | |
| UPB_PRIVATE(upb_generated_extension_list) = NULL; | |
| typedef struct upb_GeneratedRegistry { | |
| UPB_ATOMIC(upb_GeneratedRegistryRef*) ref; | |
| UPB_ATOMIC(int32_t) ref_count; | |
| } upb_GeneratedRegistry; | |
| static upb_GeneratedRegistry* _upb_generated_registry(void) { | |
| static upb_GeneratedRegistry r = {NULL, 0}; | |
| return &r; | |
| } | |
| static bool _upb_GeneratedRegistry_AddAllLinkedExtensions( | |
| upb_ExtensionRegistry* r) { | |
| const UPB_PRIVATE(upb_GeneratedExtensionListEntry)* entry = | |
| UPB_PRIVATE(upb_generated_extension_list); | |
| while (entry != NULL) { | |
| const upb_MiniTableExtension** current = entry->start; | |
| for (current = entry->start; current != entry->stop; ++current) { | |
| const upb_MiniTableExtension* ext = *current; | |
| // Sentinels and padding introduced by the linker can result in zeroed | |
| // entries, so simply skip them. | |
| if (*current == NULL) { | |
| // MSVC introduces padding that might not be sized exactly the same as | |
| // the linker array element, but it should be properly aligned, so just | |
| // skipping empty elements should be safe. (If the size and align of | |
| // the array elements was different, we'd have to do something more | |
| // complicated). | |
| continue; | |
| } | |
| if (upb_ExtensionRegistry_Add(r, ext) != | |
| kUpb_ExtensionRegistryStatus_Ok) { | |
| return false; | |
| } | |
| } | |
| entry = entry->next; | |
| } | |
| return true; | |
| } | |
| // Constructs a new GeneratedRegistryRef, adding all linked extensions to the | |
| // registry or returning NULL on failure. | |
| static upb_GeneratedRegistryRef* _upb_GeneratedRegistry_New(void) { | |
| upb_Arena* arena = NULL; | |
| upb_ExtensionRegistry* extreg = NULL; | |
| upb_GeneratedRegistryRef* ref = upb_gmalloc(sizeof(upb_GeneratedRegistryRef)); | |
| if (ref == NULL) goto err; | |
| arena = upb_Arena_New(); | |
| if (arena == NULL) goto err; | |
| extreg = upb_ExtensionRegistry_New(arena); | |
| if (extreg == NULL) goto err; | |
| ref->UPB_PRIVATE(arena) = arena; | |
| ref->UPB_PRIVATE(registry) = extreg; | |
| if (!_upb_GeneratedRegistry_AddAllLinkedExtensions(extreg)) goto err; | |
| return ref; | |
| err: | |
| if (arena != NULL) upb_Arena_Free(arena); | |
| if (ref != NULL) upb_gfree(ref); | |
| return NULL; | |
| } | |
| const upb_GeneratedRegistryRef* upb_GeneratedRegistry_Load(void) { | |
| upb_GeneratedRegistry* registry = _upb_generated_registry(); | |
| // Loop until we successfully acquire a reference. This loop should only | |
| // kick in under extremely high contention, and it should be guaranteed to | |
| // succeed. | |
| while (true) { | |
| int32_t count = upb_Atomic_Load(®istry->ref_count, memory_order_acquire); | |
| // Try to increment the refcount, but only if it's not zero. | |
| while (count > 0) { | |
| if (upb_Atomic_CompareExchangeStrong(®istry->ref_count, &count, | |
| count + 1, memory_order_acquire, | |
| memory_order_relaxed)) { | |
| // Successfully incremented. We can now safely load and return the | |
| // pointer. | |
| const upb_GeneratedRegistryRef* ref = | |
| upb_Atomic_Load(®istry->ref, memory_order_acquire); | |
| UPB_ASSERT(ref != NULL); | |
| return ref; | |
| } | |
| // CAS failed, `count` was updated. Loop will retry. | |
| } | |
| // If we're here, the count was 0. Time for the slow path. | |
| // Double-check that the pointer is NULL before trying to create. | |
| upb_GeneratedRegistryRef* ref = | |
| upb_Atomic_Load(®istry->ref, memory_order_acquire); | |
| if (ref == NULL) { | |
| // Pointer is NULL, try to create and publish a new registry. | |
| upb_GeneratedRegistryRef* new_ref = _upb_GeneratedRegistry_New(); | |
| if (new_ref == NULL) return NULL; // OOM | |
| // Try to CAS the pointer from NULL to our new_ref. | |
| if (upb_Atomic_CompareExchangeStrong(®istry->ref, &ref, new_ref, | |
| memory_order_release, | |
| memory_order_acquire)) { | |
| // We won the race. Set the ref count to 1. | |
| upb_Atomic_Store(®istry->ref_count, 1, memory_order_release); | |
| return new_ref; | |
| } else { | |
| // We lost the race. `ref` now holds the pointer from the winning | |
| // thread. Clean up our unused one and loop to try again to get a | |
| // reference. | |
| upb_Arena_Free(new_ref->UPB_PRIVATE(arena)); | |
| upb_gfree(new_ref); | |
| } | |
| } | |
| // If we are here, either we lost the CAS race, or the pointer was already | |
| // non-NULL. In either case, we loop to the top and try to increment the | |
| // refcount of the existing object. | |
| #if UPB_TSAN | |
| // Yield to give other threads a chance to increment the refcount. This is | |
| // especially an issue for TSAN builds, which are prone to locking up from | |
| // the thread with the upb_Atomic_Store call above getting starved. | |
| sched_yield(); | |
| #endif // UPB_TSAN | |
| } | |
| } | |
| void upb_GeneratedRegistry_Release(const upb_GeneratedRegistryRef* r) { | |
| if (r == NULL) return; | |
| upb_GeneratedRegistry* registry = _upb_generated_registry(); | |
| int ref_count = upb_Atomic_Sub(®istry->ref_count, 1, memory_order_acq_rel); | |
| UPB_ASSERT(registry->ref_count >= 0); | |
| // A ref_count of 1 means that we decremented the refcount to 0. | |
| if (ref_count == 1) { | |
| upb_GeneratedRegistryRef* ref = | |
| upb_Atomic_Exchange(®istry->ref, NULL, memory_order_acq_rel); | |
| if (ref != NULL) { | |
| // This is the last reference and we won any potential race to store NULL, | |
| // so we need to clean up. | |
| upb_Arena_Free(ref->UPB_PRIVATE(arena)); | |
| upb_gfree(ref); | |
| } | |
| } | |
| } | |
| const upb_ExtensionRegistry* upb_GeneratedRegistry_Get( | |
| const upb_GeneratedRegistryRef* r) { | |
| if (r == NULL) return NULL; | |
| return r->UPB_PRIVATE(registry); | |
| } |