| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/fit/defer.h> |
| #include <lib/fit/function.h> |
| #include <lib/fit/result.h> |
| #include <lib/memalloc/pool.h> |
| #include <lib/memalloc/range.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <zircon/assert.h> |
| |
| #include <algorithm> |
| #include <bit> |
| #include <cstddef> |
| #include <cstdint> |
| #include <iterator> |
| #include <span> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| |
| #include "algorithm.h" |
| |
| namespace memalloc { |
| |
| namespace { |
| |
| constexpr uint64_t kMax = std::numeric_limits<uint64_t>::max(); |
| |
| constexpr std::optional<uint64_t> Align(uint64_t addr, uint64_t alignment) { |
| ZX_DEBUG_ASSERT(std::has_single_bit(alignment)); |
| |
| // If `addr + alignment - 1` overflows, then that means addr lies within |
| // [2^64 - alignment + 1, 2^64), from which it should be clear that it is not |
| // aligned and nor can it be. |
| if (unlikely(addr > kMax - (alignment - 1))) { |
| return {}; |
| } |
| return (addr + alignment - 1) & ~(alignment - 1); |
| } |
| |
| // A shared routine for the const and mutable versions of |
| // Pool::FindContainingRange() below. |
| template <class Ranges> |
| auto FindContainingRangeAmong(Ranges&& ranges, uint64_t addr, uint64_t size) |
| -> decltype(ranges.begin()) { |
| ZX_DEBUG_ASSERT(size <= kMax - addr); |
| |
| // Despite the name, this function gives us the first range that is |
| // lexicographically >= [addr, addr + size) |
| auto next = std::lower_bound(ranges.begin(), ranges.end(), Range{.addr = addr, .size = size}); |
| uint64_t range_end = addr + size; |
| if (next != ranges.end() && addr >= next->addr) { |
| return range_end <= next->end() ? next : ranges.end(); |
| } |
| // If the first range lexicographically >= [addr, addr + size) is |
| // ranges_.begin() and we did not enter the previous branch, then addr + size |
| // exceeds the right endpoint of ranges_.begin(). |
| if (next == ranges.begin()) { |
| return ranges.end(); |
| } |
| auto prev = std::prev(next); |
| return (prev->addr <= addr && range_end <= prev->end()) ? prev : ranges.end(); |
| } |
| |
| } // namespace |
| |
| fit::result<fit::failed> Pool::Init(std::span<internal::RangeIterationContext> state, |
| uint64_t min_addr, uint64_t max_addr) { |
| RangeStream ranges(state); |
| |
| const size_t scratch_size = FindNormalizedRangesScratchSize(ranges.size()) * sizeof(void*); |
| |
| // We want enough bookkeeping to fit our initial pages as well as the |
| // scratch buffer we will need for FindNormalizedRanges(). |
| const uint64_t bookkeeping_size = |
| *Align(ranges.size() * sizeof(Node) + scratch_size, kBookkeepingChunkSize); |
| |
| std::optional<uint64_t> bookkeeping_addr; |
| auto find_bookkeeping = [min_addr, max_addr, bookkeeping_size, |
| &bookkeeping_addr](const Range& range) { |
| ZX_DEBUG_ASSERT(range.type == Type::kFreeRam); |
| |
| const uint64_t start = std::max(range.addr, min_addr); |
| const uint64_t end = std::min(range.end(), max_addr); |
| |
| std::optional<uint64_t> aligned_start = Align(start, kBookkeepingChunkSize); |
| if (!aligned_start || *aligned_start >= end || end - *aligned_start < bookkeeping_size) { |
| return true; |
| } |
| // Found our bookkeeping space. |
| bookkeeping_addr = aligned_start; |
| return false; |
| }; |
| FindNormalizedRamRanges(ranges, find_bookkeeping); |
| if (!bookkeeping_addr) { |
| return fit::failed(); |
| } |
| |
| // Make the prospective bookkeeping region accessible before any access. |
| access_callback_(*bookkeeping_addr, bookkeeping_size); |
| |
| // Convert our bookkeeping space before actual use, and zero out the mapped |
| // area to be able to recast as Nodes in the valid, initial fbl linked list |
| // node state. `[0, bookkeeping.size() - scratch_size)` of that space will |
| // then be turned into unused nodes. The tail of FindNormalizedRanges() |
| // scratch space will be reclaimed after we are done with it. |
| std::byte* bookkeeping_ptr = bookkeeping_pointer_(*bookkeeping_addr, bookkeeping_size); |
| std::span<void*> find_scratch = { |
| reinterpret_cast<void**>(bookkeeping_ptr + bookkeeping_size - scratch_size), |
| scratch_size / sizeof(void*), |
| }; |
| const std::byte* bookkeeping_end = bookkeeping_ptr + bookkeeping_size; |
| bookkeeping_ptr = PopulateAsBookkeeping(bookkeeping_ptr, bookkeeping_size - scratch_size); |
| ZX_ASSERT(bookkeeping_ptr); |
| |
| ranges.reset(); |
| bool alloc_failure = false; |
| auto process_range = [this, &alloc_failure](const Range& range) { |
| // Amongst normalized ranges, reserved ranges are just 'holes'. |
| if (range.type == Type::kReserved) { |
| return true; |
| } |
| |
| if (auto result = NewNode(range); result.is_error()) { |
| alloc_failure = true; |
| return false; |
| } else { |
| Node* node = std::move(result).value(); |
| AppendNode(node); |
| } |
| return true; |
| }; |
| ZX_ASSERT_MSG(memalloc::FindNormalizedRanges(ranges, find_scratch, process_range).is_ok(), |
| "Pool::Init(): bad input: the provided ranges feature overlap among different " |
| "allocated types, or an allocated type with one of kReserved or kPeripheral"); |
| if (alloc_failure) { |
| return fit::failed(); |
| } |
| |
| // Now reclaim the tail as node space. |
| PopulateAsBookkeeping(bookkeeping_ptr, bookkeeping_end - bookkeeping_ptr); |
| |
| // Track the bookkeeping range, so it is not later allocated. |
| const Range bookkeeping{ |
| .addr = *bookkeeping_addr, |
| .size = bookkeeping_size, |
| .type = Type::kPoolBookkeeping, |
| }; |
| if (auto result = InsertSubrange(bookkeeping); result.is_error()) { |
| return result.take_error(); |
| } |
| |
| default_min_addr_ = min_addr; |
| default_max_addr_ = max_addr; |
| return fit::ok(); |
| } |
| |
| fit::result<fit::failed, Pool::Node*> Pool::NewNode(const Range& range) { |
| ZX_DEBUG_ASSERT(range.type != Type::kReserved); // Not tracked, by policy. |
| |
| if (unused_.is_empty()) { |
| return fit::failed(); |
| } |
| Range* node = unused_.pop_back(); |
| ZX_DEBUG_ASSERT(node); |
| node->addr = range.addr; |
| node->size = range.size; |
| node->type = range.type; |
| return fit::ok(static_cast<Node*>(node)); |
| } |
| |
| fit::result<fit::failed, uint64_t> Pool::Allocate(Type type, uint64_t size, uint64_t alignment, |
| std::optional<uint64_t> min_addr, |
| std::optional<uint64_t> max_addr) { |
| ZX_ASSERT(size > 0); |
| uint64_t upper_bound = max_addr.value_or(default_max_addr_); |
| uint64_t lower_bound = min_addr.value_or(default_min_addr_); |
| ZX_ASSERT(lower_bound <= upper_bound); |
| |
| TryToEnsureTwoBookkeepingNodes(); |
| |
| uint64_t addr = 0; |
| if (auto result = FindAllocatable(type, size, alignment, lower_bound, upper_bound); |
| result.is_error()) { |
| return result; |
| } else { |
| addr = std::move(result).value(); |
| } |
| |
| const Range allocated{.addr = addr, .size = size, .type = type}; |
| if (auto result = InsertSubrange(allocated); result.is_error()) { |
| return result.take_error(); |
| } else { |
| auto it = std::move(result).value(); |
| Coalesce(it); |
| } |
| |
| access_callback_(addr, size); |
| return fit::ok(addr); |
| } |
| |
| fit::result<fit::failed> Pool::MarkAsPeripheral(const memalloc::Range& range) { |
| ZX_ASSERT(range.type == memalloc::Type::kPeripheral); |
| if (range.size == 0) { |
| return fit::success(); |
| } |
| |
| // Find the `lower_bound` such that it is the first range intersecting with |
| // `range` or the greatest lower bound of `range` in `ranges_`. |
| mutable_iterator lower_bound = ranges_.end(); |
| for (mutable_iterator it = ranges_.begin(); it != ranges_.end(); ++it) { |
| // First interseting range. |
| if (it->IntersectsWith(range)) { |
| if (it->type != memalloc::Type::kPeripheral) { |
| return fit::failed(); |
| } |
| lower_bound = it; |
| break; |
| } |
| |
| // Previous *it is the greatest lower bound for `range` in `ranges_`, |
| // and `lower_bound` is already set to it. |
| if (!(*it < range)) { |
| break; |
| } |
| lower_bound = it; |
| } |
| |
| // Find an `upper_bound` such that is the last intersecting range with `range` |
| // or is the least upper bound of `range` in `ranges_`. |
| mutable_iterator upper_bound = ranges_.end(); |
| for (mutable_iterator it = lower_bound; it != ranges_.end(); ++it) { |
| if (it->IntersectsWith(range)) { |
| // An overlapping range MUST be of the same type. |
| if (it->type != memalloc::Type::kPeripheral) { |
| return fit::failed(); |
| } |
| } else if (range < *it) { |
| upper_bound = it; |
| break; |
| } |
| } |
| |
| // NOTE: |
| // At this point, there are no 'non-Peripheral' intersecting with `range` within [`lower_bound`, |
| // `upper_bound`]. This has been verified when searching for both bounds. |
| |
| auto insert_range_at = [&range, this](mutable_iterator it) -> fit::result<fit::failed> { |
| Node* node = nullptr; |
| if (auto result = NewNode(range); result.is_ok()) { |
| node = std::move(result).value(); |
| } else { |
| return result.take_error(); |
| } |
| |
| ZX_DEBUG_ASSERT(node); |
| TryToEnsureTwoBookkeepingNodes(); |
| Coalesce(InsertNodeAt(node, it)); |
| return fit::success(); |
| }; |
| |
| if (lower_bound == ranges_.end()) { |
| return insert_range_at(ranges_.begin()); |
| } |
| |
| // The `first_intersecting_range` can be reused for merging range, and any |
| // overlapping ranges into it. |
| auto first_intersecting_range = |
| std::find_if(lower_bound, upper_bound, |
| [&range](const auto& other) { return range.IntersectsWith(other); }); |
| |
| // The result of std::find_if means "not found" if returns `upper_bound` and |
| // that points to end() or to a non-intersecting range. |
| if (first_intersecting_range == ranges_.end() || |
| !first_intersecting_range->IntersectsWith(range)) { |
| return insert_range_at(std::next(lower_bound)); |
| } |
| |
| auto merge_ranges = [](mutable_iterator accumulator, const memalloc::Range& range) { |
| uint64_t accumulator_end = accumulator->end(); |
| accumulator->addr = std::min(accumulator->addr, range.addr); |
| accumulator->size = |
| static_cast<size_t>(std::max(accumulator_end, range.end()) - accumulator->addr); |
| }; |
| |
| // Make sure that the node reused for storage, contains range. |
| merge_ranges(first_intersecting_range, range); |
| |
| for (auto it = std::next(first_intersecting_range); it != upper_bound;) { |
| merge_ranges(first_intersecting_range, *it); |
| auto remove_it = it++; |
| RemoveNodeAt(remove_it); |
| } |
| |
| // Try merge with adjacent ranges if applicable, when `lower_bound` or `upper_bound` are |
| // peripheral ranges adjacent to `range`. |
| Coalesce(first_intersecting_range); |
| return fit::success(); |
| } |
| |
| fit::result<fit::failed> Pool::CoalescePeripherals(std::span<const size_t> alignments) { |
| for (auto curr = ranges_.begin(), prev = ranges_.end(); curr != ranges_.end();) { |
| if (curr->type != memalloc::Type::kPeripheral) { |
| prev = curr++; |
| continue; |
| } |
| auto last = curr; |
| for (auto next = std::next(curr); next != ranges_.end(); ++next) { |
| if (next->type != memalloc::Type::kPeripheral) { |
| break; |
| } |
| last = next; |
| } |
| |
| // `prev` is the non peripheral range preceding `curr`. |
| // `curr` is the first peripheral range in this run. |
| // `last` is the last peripheral range in the run. |
| // |
| // `prev->end()` and `std::next(last)->end()` will determine the limits |
| // for the inflated range. And `curr->addr` and `last->end()` will determine |
| // the minimum range to cover. |
| const uint64_t min_base_addr = prev == ranges_.end() ? 0 : prev->end(); |
| const uint64_t max_base_addr = curr->addr; |
| const uint64_t min_end_address = last->end(); |
| auto next = std::next(last); |
| const uint64_t max_end_address = |
| next == ranges_.end() ? std::numeric_limits<uint64_t>::max() : next->addr; |
| |
| // Try alignments from first to last, first viable alignment, we keep. |
| // These allows to adjust in cases where given a layout we can't pick |
| // biggest alignment but second biggest, thus minimizing the number of |
| // pages required to map this range. |
| uint64_t base_address = min_base_addr; |
| uint64_t end_address = min_end_address; |
| |
| // Handle the unlikely case were we may not be able to generate larger ranges |
| // due to layout. |
| for (auto align : alignments) { |
| const uint64_t alignment = 1ull << align; |
| uint64_t tmp_base_address = fbl::round_down(max_base_addr, alignment); |
| uint64_t tmp_end_address = fbl::round_up(min_end_address, alignment); |
| if (tmp_base_address < min_base_addr) { |
| continue; |
| } |
| if (tmp_end_address > max_end_address) { |
| continue; |
| } |
| base_address = tmp_base_address; |
| end_address = tmp_end_address; |
| break; |
| } |
| |
| // Might be invalidated by merging ranges afterwards. |
| curr = ++last; |
| auto res = MarkAsPeripheral({.addr = base_address, |
| .size = end_address - base_address, |
| .type = memalloc::Type::kPeripheral}); |
| if (res.is_error()) { |
| return res; |
| } |
| } |
| |
| return fit::ok(); |
| } |
| |
| fit::result<fit::failed, uint64_t> Pool::FindAllocatable(Type type, uint64_t size, |
| uint64_t alignment, uint64_t min_addr, |
| uint64_t max_addr) { |
| ZX_DEBUG_ASSERT(IsAllocatedType(type)); |
| ZX_DEBUG_ASSERT(size > 0); |
| ZX_DEBUG_ASSERT(min_addr <= max_addr); |
| if (size - 1 > max_addr - min_addr) { |
| return fit::failed(); |
| } |
| |
| // We use a simple first-fit approach, ultimately assuming that allocation |
| // patterns will not create a lot of fragmentation. |
| for (const Range& range : *this) { |
| if (range.type != Type::kFreeRam || range.end() <= min_addr) { |
| continue; |
| } |
| if (range.addr > max_addr) { |
| break; |
| } |
| |
| // If we have already aligned past UINT64_MAX or the prescribed maximum |
| // address, then the same will be true with any subsequent ranges, so we |
| // can short-circuit now. |
| std::optional<uint64_t> aligned = Align(std::max(range.addr, min_addr), alignment); |
| if (!aligned || *aligned > max_addr - size + 1) { |
| break; |
| } |
| |
| if (*aligned >= range.end() || range.end() - *aligned < size) { |
| continue; |
| } |
| return fit::ok(*aligned); |
| } |
| |
| return fit::failed(); |
| } |
| |
| fit::result<fit::failed> Pool::Free(uint64_t addr, uint64_t size) { |
| ZX_ASSERT(kMax - addr >= size); |
| |
| if (size == 0) { |
| // Nothing to do. |
| return fit::ok(); |
| } |
| |
| auto it = FindContainingRange(addr, size); |
| ZX_ASSERT_MSG(it != ranges_.end(), "Pool::Free(): provided address range is untracked"); |
| |
| // Double-freeing is a no-op. |
| if (it->type == Type::kFreeRam) { |
| return fit::ok(); |
| } |
| |
| // Try to proactively ensure two bookkeeping nodes, which might be required |
| // by InsertSubrange() below. |
| TryToEnsureTwoBookkeepingNodes(); |
| |
| ZX_ASSERT(it->type != Type::kPoolBookkeeping); |
| ZX_ASSERT(IsAllocatedType(it->type)); |
| const Range range{.addr = addr, .size = size, .type = Type::kFreeRam}; |
| if (auto status = InsertSubrange(range, it); status.is_error()) { |
| return status.take_error(); |
| } else { |
| it = std::move(status).value(); |
| } |
| Coalesce(it); |
| |
| return fit::ok(); |
| } |
| |
| fit::result<fit::failed, uint64_t> Pool::Resize(const Range& original, uint64_t new_size, |
| uint64_t min_alignment) { |
| ZX_ASSERT(new_size > 0); |
| ZX_ASSERT(IsAllocatedType(original.type)); |
| ZX_ASSERT(std::has_single_bit(min_alignment)); |
| ZX_ASSERT(original.addr % min_alignment == 0); |
| |
| auto it = FindContainingRange(original.addr, original.size); |
| ZX_ASSERT_MSG(it != ranges_.end(), "`original` is not a subset of a tracked range"); |
| |
| // Already appropriately sized; nothing to do. |
| if (new_size == original.size) { |
| return fit::ok(original.addr); |
| } |
| |
| // Smaller size; need only to free the tail. |
| if (new_size < original.size) { |
| if (auto result = Free(original.addr + new_size, original.size - new_size); result.is_error()) { |
| return fit::failed(); |
| } |
| return fit::ok(original.addr); |
| } |
| |
| // |
| // The strategy here onward is to see whether we can find a resize candidate |
| // that overlaps with `original`. If so, then we commit to that and directly |
| // update the relevant iterators to reflect the post-resize state; if not, |
| // then we can reallocate with knowledge that nothing could possibly be |
| // allocated into original range's space, allowing us to delay freeing it |
| // until the end without fear of poor memory utilization. |
| // |
| |
| // Now we consider to what extent we have space off the end of `original` to |
| // resize into. This is only kosher if `original` is the tail of its tracked |
| // parent range (so that there aren't any separate, previously-coalesced |
| // ranges in the way) and if there is an adjacent free RAM range present to |
| // spill over into. |
| auto next = std::next(it); |
| uint64_t wiggle_room_end = original.end(); |
| if (next != ranges_.end() && original.end() == next->addr && next->type == Type::kFreeRam) { |
| ZX_DEBUG_ASSERT(it->end() == original.end()); |
| wiggle_room_end = next->end(); |
| |
| // Can extend in place. |
| if (wiggle_room_end - original.addr >= new_size) { |
| uint64_t next_spillover = new_size - original.size; |
| if (next->size == next_spillover) { |
| RemoveNodeAt(next); |
| } else { |
| next->addr += next_spillover; |
| next->size -= next_spillover; |
| } |
| it->size += next_spillover; |
| return fit::ok(original.addr); |
| } |
| } |
| |
| // At this point, we might have a little room in the next range to spill over |
| // into, but any range overlapping with `original` would need to spill over |
| // into the previous. |
| uint64_t need = new_size - (wiggle_room_end - original.addr); |
| auto prev = it == ranges_.begin() ? ranges_.end() : std::prev(it); |
| if (prev != ranges_.end() && prev->end() == it->addr && // Adjacent. |
| prev->type == Type::kFreeRam && // Free RAM. |
| prev->size >= need && // Enough space (% alignment). |
| it->addr == original.addr) { // No coalesced ranges in the way. |
| |
| // Can take the maximal, aligned address at least `need` bytes away from |
| // the original range as a candidate for the new root, which will only work |
| // if it still lies within the previous range and isn't far enough away |
| // that we wouldn't have overlap with `original`. |
| uint64_t new_addr = (prev->end() - need) & -(min_alignment - 1); |
| if (new_addr >= prev->addr && original.addr - new_addr < new_size) { |
| uint64_t prev_spillover = original.addr - new_addr; |
| if (prev->size == prev_spillover) { |
| RemoveNodeAt(prev); |
| } else { |
| prev->size -= prev_spillover; |
| } |
| it->addr -= prev_spillover; |
| it->size += prev_spillover; |
| |
| // If the new end spills over into the next range, we must update the |
| // bookkeeping there; if it falls short of the original end, then there |
| // is nothing left to do but free the tail. |
| if (uint64_t new_end = new_addr + new_size; new_end > original.end()) { |
| ZX_DEBUG_ASSERT(next != ranges_.end()); |
| ZX_DEBUG_ASSERT(next->addr == original.end()); |
| ZX_DEBUG_ASSERT(next->type == Type::kFreeRam); |
| |
| uint64_t next_spillover = new_end - original.end(); |
| if (next->size == next_spillover) { |
| RemoveNodeAt(next); |
| } else { |
| next->addr += next_spillover; |
| next->size -= next_spillover; |
| } |
| it->size += next_spillover; |
| ZX_DEBUG_ASSERT(it->size >= new_size); |
| } else if (new_end < original.end()) { |
| auto result = Free(new_end, original.end() - new_end); |
| if (result.is_error()) { |
| return fit::failed(); |
| } |
| } |
| return fit::ok(new_addr); |
| } |
| } |
| |
| // No option left but to allocate a replacement. |
| uint64_t new_addr = 0; |
| if (auto result = Allocate(original.type, new_size, min_alignment); result.is_error()) { |
| return fit::failed(); |
| } else { |
| new_addr = std::move(result).value(); |
| } |
| if (auto result = Free(original.addr, original.size); result.is_error()) { |
| return fit::failed(); |
| } |
| return fit::ok(new_addr); |
| } |
| |
| fit::result<fit::failed> Pool::UpdateRamSubranges(Type type, uint64_t addr, uint64_t size) { |
| ZX_ASSERT(IsAllocatedType(type)); |
| ZX_ASSERT(kMax - addr >= size); |
| |
| if (size == 0) { |
| // Nothing to do. |
| return fit::ok(); |
| } |
| |
| // Try to proactively ensure two bookkeeping nodes, which might be required |
| // by InsertSubrange() below. |
| TryToEnsureTwoBookkeepingNodes(); |
| |
| mutable_iterator it = ranges_.begin(); |
| while (it != ranges_.end() && addr + size > it->addr) { |
| if (addr < it->end() && IsRamType(it->type)) { |
| uint64_t first = std::max(it->addr, addr); |
| uint64_t last = std::min(it->end(), addr + size); |
| const Range range{.addr = first, .size = last - first, .type = type}; |
| if (auto status = InsertSubrange(range, it); status.is_error()) { |
| return status.take_error(); |
| } else { |
| it = std::move(status).value(); |
| } |
| it = Coalesce(it); |
| } |
| ++it; |
| } |
| return fit::ok(); |
| } |
| |
| fit::result<fit::failed> Pool::TruncateTotalRam(uint64_t new_capacity_bytes) { |
| for (mutable_iterator it = ranges_.begin(); it != ranges_.end(); ++it) { |
| if (!IsRamType(it->type) || it->type == Type::kNvram) { |
| continue; |
| } |
| |
| // RAM fits within budget. |
| if (it->size <= new_capacity_bytes) { |
| new_capacity_bytes -= it->size; |
| continue; |
| } |
| |
| // RAM does not wholly fit within budget. We fit the head of the range if |
| // appropriate and truncate the rest. |
| uint64_t start = it->addr + new_capacity_bytes; |
| Range truncated_tail{ |
| .addr = start, |
| .size = it->end() - start, |
| .type = Type::kTruncatedRam, |
| }; |
| if (auto result = InsertSubrange(truncated_tail, it); result.is_error()) { |
| return result.take_error(); |
| } else { |
| it = Coalesce(result.value()); |
| } |
| new_capacity_bytes = 0; |
| } |
| return fit::ok(); |
| } |
| |
| fit::result<fit::failed, Pool::mutable_iterator> Pool::InsertSubrange( |
| const Range& range, std::optional<mutable_iterator> parent_it) { |
| auto it = parent_it.value_or(FindContainingRange(range.addr, range.size)); |
| ZX_DEBUG_ASSERT(it != ranges_.end()); |
| |
| // .------------. |
| // | //////// | |
| // '------------' |
| // <---range----> |
| // <----*it-----> |
| if (it->addr == range.addr && it->size == range.size) { |
| it->type = range.type; |
| return fit::ok(it); |
| } |
| |
| // We know now that we will need at least one new node for `range`. |
| Node* node = nullptr; |
| if (auto result = NewNode(range); result.is_error()) { |
| return result.take_error(); |
| } else { |
| node = std::move(result).value(); |
| } |
| ZX_DEBUG_ASSERT(node); |
| |
| // .------------+------------. |
| // | //////// | | |
| // '------------+------------' |
| // <---range----> |
| // <----------*it------------> |
| if (it->addr == range.addr) { |
| ZX_DEBUG_ASSERT(range.size < it->size); |
| it->addr += range.size; |
| it->size -= range.size; |
| return fit::ok(InsertNodeAt(node, it)); |
| } |
| |
| const uint64_t containing_end = it->end(); |
| auto next = std::next(it); |
| |
| // .------------+------------. |
| // | | //////// | |
| // '------------+------------' |
| // <---range----> |
| // <-----------*it-----------> |
| if (range.end() == it->end()) { |
| ZX_DEBUG_ASSERT(it->addr < range.addr); |
| it->size -= range.size; |
| return fit::ok(InsertNodeAt(node, next)); |
| } |
| |
| // .------------+------------.------------. |
| // | | //////// | | |
| // '------------+------------'------------' |
| // <---range----> |
| // <-----------------*it------------------> |
| ZX_DEBUG_ASSERT(it->addr < range.addr); |
| ZX_DEBUG_ASSERT(range.end() < containing_end); |
| |
| Range after = { |
| .addr = range.end(), |
| .size = containing_end - range.end(), |
| .type = it->type, |
| }; |
| if (auto result = NewNode(after); result.is_error()) { |
| return result.take_error(); |
| } else { |
| // Save modifications until we know we have sufficient nodes to actually |
| // perform the insertion. |
| it->size = range.addr - it->addr; |
| InsertNodeAt(node, next); |
| |
| node = std::move(result).value(); |
| ZX_DEBUG_ASSERT(node); |
| InsertNodeAt(node, next); |
| } |
| |
| return fit::ok(std::next(it)); |
| } |
| |
| Pool::iterator Pool::FindContainingRange(uint64_t addr, uint64_t size) const { |
| return FindContainingRangeAmong(ranges_, addr, size); |
| } |
| |
| Pool::mutable_iterator Pool::FindContainingRange(uint64_t addr, uint64_t size) { |
| return FindContainingRangeAmong(ranges_, addr, size); |
| } |
| |
| Pool::mutable_iterator Pool::Coalesce(mutable_iterator it) { |
| if (it != ranges_.begin()) { |
| auto prev = std::prev(it); |
| if (prev->type == it->type && prev->end() == it->addr) { |
| it->addr = prev->addr; |
| it->size += prev->size; |
| unused_.push_back(RemoveNodeAt(prev)); |
| } |
| } |
| if (it != ranges_.end()) { |
| auto next = std::next(it); |
| if (next != ranges_.end() && next->type == it->type && it->end() == next->addr) { |
| it->size += next->size; |
| unused_.push_back(RemoveNodeAt(next)); |
| } |
| } |
| return it; |
| } |
| |
| void Pool::TryToEnsureTwoBookkeepingNodes() { |
| // Instead of iterating through `unused_` to compute the size, we make the |
| // following O(1) check instead. |
| auto begin = unused_.begin(); |
| auto end = unused_.end(); |
| bool one_or_less = begin == end || std::next(begin) == end; |
| if (!one_or_less) { |
| return; |
| } |
| |
| uint64_t addr = 0; |
| if (auto result = FindAllocatable(Type::kPoolBookkeeping, kBookkeepingChunkSize, |
| kBookkeepingChunkSize, default_min_addr_, default_max_addr_); |
| result.is_error()) { |
| return; |
| } else { |
| addr = std::move(result).value(); |
| } |
| |
| // Make the prospective bookkeeping region accessible before any access. |
| access_callback_(addr, kBookkeepingChunkSize); |
| |
| std::byte* ptr = bookkeeping_pointer_(addr, kBookkeepingChunkSize); |
| ZX_ASSERT(ptr); |
| PopulateAsBookkeeping(ptr, kBookkeepingChunkSize); |
| |
| const Range bookkeeping = { |
| .addr = addr, |
| .size = kBookkeepingChunkSize, |
| .type = Type::kPoolBookkeeping, |
| }; |
| // Don't bother to check for errors: we have already populated the new |
| // bookkeeping chunk, so things should succeed; else, we are in a pathological |
| // state and should fail hard. |
| auto it = InsertSubrange(bookkeeping).value(); |
| Coalesce(it); |
| } |
| |
| std::byte* Pool::PopulateAsBookkeeping(std::byte* addr, uint64_t size) { |
| ZX_DEBUG_ASSERT(addr); |
| ZX_DEBUG_ASSERT(size <= kMax - reinterpret_cast<uint64_t>(addr)); |
| |
| memset(addr, 0, static_cast<size_t>(size)); |
| std::byte* end = addr + size; |
| while (addr < end && end - addr >= static_cast<int>(sizeof(Node))) { |
| unused_.push_back(reinterpret_cast<Range*>(addr)); |
| addr += sizeof(Node); |
| } |
| return addr; |
| } |
| |
| void Pool::AppendNode(Node* node) { |
| ++num_ranges_; |
| ranges_.push_back(node); |
| } |
| |
| Pool::mutable_iterator Pool::InsertNodeAt(Node* node, mutable_iterator it) { |
| ++num_ranges_; |
| return ranges_.insert(it, node); |
| } |
| |
| Pool::Node* Pool::RemoveNodeAt(mutable_iterator it) { |
| ZX_DEBUG_ASSERT(num_ranges_ > 0); |
| --num_ranges_; |
| return static_cast<Node*>(ranges_.erase(it)); |
| } |
| |
| } // namespace memalloc |