From 46f288d65ea31b57757cbcd96c744c67bde6c8ec Mon Sep 17 00:00:00 2001 From: Jim Borden Date: Sat, 20 Jun 2026 10:16:48 +0900 Subject: [PATCH] CBL-8494: Use bit 1 instead of the high bit for AtomicWrapper busy flag AtomicWrapper's spinlock stored its "busy" flag in the high bit of the pointer (bit 63 on 64-bit). On 64-bit Android, Bionic's tagged pointers (TBI) place an allocator tag in the top byte of every heap pointer, so bit 63 is frequently already set. getAndLock() then reads the slot as permanently busy and spins forever, wedging the owning thread with no crash or ANR. The high bit is also a real address bit on 32-bit ABIs. Move the flag to bit 1 (value 2). RefCounted has an atomic member, so it is always >= 4-aligned and bits 0 and 1 are guaranteed zero in any stored pointer on every ABI. Bit 0 stays reserved for Fleece's mutable-Value low-bit tag. getAndLock() already returns the bit-cleared value, so no dereference-site masking is needed. Co-Authored-By: Claude Opus 4.8 --- Fleece/Support/RefCounted.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Fleece/Support/RefCounted.cc b/Fleece/Support/RefCounted.cc index 72185e98..f706e221 100644 --- a/Fleece/Support/RefCounted.cc +++ b/Fleece/Support/RefCounted.cc @@ -135,8 +135,11 @@ namespace fleece { namespace internal { /// Tag bit that's added to `_ref` while accessing it. - /// We can't use the low bit (1) because mutable Fleece Values already use that as a tag. - static constexpr uintptr_t kBusyMask = uintptr_t(1) << (8 * sizeof(uintptr_t) - 1); + /// We use bit 1 (value 2): RefCounted has an `atomic` member, so it's always + /// >= 4-aligned, guaranteeing bits 0 and 1 are zero in any stored pointer on every ABI. + /// Bit 0 (value 1) is reserved because mutable Fleece Values already use it as a tag. + /// (The previous high-bit choice collided with Android's arm64 tagged pointers; see CBL-8494.) + static constexpr uintptr_t kBusyMask = 0b10; AtomicWrapper::AtomicWrapper(uintptr_t ref) noexcept :_ref(ref)