| // Copyright 2025 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_allocator/internal/control_block.h" |
| |
| #include "lib/stdcompat/bit.h" |
| #include "pw_allocator/allocator.h" |
| #include "pw_allocator/hardening.h" |
| #include "pw_assert/check.h" |
| #include "pw_bytes/alignment.h" |
| |
| namespace pw::allocator::internal { |
| |
| ControlBlock* ControlBlock::Create(Allocator* allocator, |
| Layout layout, |
| size_t size) { |
| layout = layout.Extend(AlignUp(sizeof(ControlBlock), layout.alignment())); |
| void* ptr = allocator->Allocate(layout); |
| if (ptr == nullptr) { |
| return nullptr; |
| } |
| auto addr = cpp20::bit_cast<uintptr_t>(ptr); |
| addr = AlignUp(addr + sizeof(ControlBlock), layout.alignment()); |
| auto* data = cpp20::bit_cast<std::byte*>(addr); |
| return new (ptr) ControlBlock(allocator, data, size); |
| } |
| |
| int32_t ControlBlock::num_shared() const noexcept { |
| return UnpackShared(num_weak_and_shared_.load(std::memory_order_relaxed)); |
| } |
| |
| bool ControlBlock::IncrementShared() { |
| uint32_t num = num_weak_and_shared_.load(std::memory_order_relaxed); |
| while (true) { |
| uint16_t num_weak = UnpackWeak(num); |
| uint16_t num_shared = UnpackShared(num); |
| if constexpr (Hardening::kIncludesDebugChecks) { |
| PW_CHECK_UINT_GE(num_weak, num_shared); |
| } |
| if (num_shared == 0 || num_shared == 0xFFFF) { |
| return false; |
| } |
| uint32_t packed = Pack(num_weak + 1, num_shared + 1); |
| if (num_weak_and_shared_.compare_exchange_weak( |
| num, packed, std::memory_order_relaxed)) { |
| return true; |
| } |
| } |
| } |
| |
| bool ControlBlock::IncrementWeak() { |
| uint32_t num = num_weak_and_shared_.load(std::memory_order_relaxed); |
| while (true) { |
| uint16_t num_weak = UnpackWeak(num); |
| uint16_t num_shared = UnpackShared(num); |
| if constexpr (Hardening::kIncludesDebugChecks) { |
| PW_CHECK_UINT_GE(num_weak, num_shared); |
| } |
| if (num_weak == 0 || num_weak == 0xFFFF) { |
| return false; |
| } |
| uint32_t packed = Pack(num_weak + 1, num_shared); |
| if (num_weak_and_shared_.compare_exchange_weak( |
| num, packed, std::memory_order_relaxed)) { |
| return true; |
| } |
| } |
| } |
| |
| ControlBlock::Action ControlBlock::DecrementShared() { |
| uint32_t prev = |
| num_weak_and_shared_.fetch_sub(Pack(1, 1), std::memory_order_acq_rel); |
| uint16_t prev_weak = UnpackWeak(prev); |
| uint16_t prev_shared = UnpackShared(prev); |
| if constexpr (Hardening::kIncludesDebugChecks) { |
| PW_CHECK_UINT_NE(prev_weak, 0); |
| PW_CHECK_UINT_NE(prev_shared, 0); |
| PW_CHECK_UINT_GE(prev_weak, prev_shared); |
| } |
| if (prev_weak == 1 && prev_shared == 1) { |
| return ControlBlock::Action::kFree; |
| } |
| if (prev_shared == 1) { |
| return ControlBlock::Action::kExpire; |
| } |
| return ControlBlock::Action::kNone; |
| } |
| |
| ControlBlock::Action ControlBlock::DecrementWeak() { |
| uint32_t prev = |
| num_weak_and_shared_.fetch_sub(Pack(1, 0), std::memory_order_acq_rel); |
| uint16_t prev_weak = UnpackWeak(prev); |
| if constexpr (Hardening::kIncludesDebugChecks) { |
| uint16_t prev_shared = UnpackShared(prev); |
| PW_CHECK_UINT_NE(prev_weak, 0); |
| PW_CHECK_UINT_GE(prev_weak, prev_shared); |
| } |
| if (prev_weak == 1) { |
| return ControlBlock::Action::kFree; |
| } |
| return ControlBlock::Action::kNone; |
| } |
| |
| } // namespace pw::allocator::internal |