| // Copyright (c) 2018 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "third_party/base/allocator/partition_allocator/partition_page.h" | |
| #include "third_party/base/allocator/partition_allocator/partition_direct_map_extent.h" | |
| #include "third_party/base/allocator/partition_allocator/partition_root_base.h" | |
| namespace pdfium { | |
| namespace base { | |
| namespace internal { | |
| namespace { | |
| ALWAYS_INLINE DeferredUnmap PartitionDirectUnmap(PartitionPage* page) { | |
| PartitionRootBase* root = PartitionRootBase::FromPage(page); | |
| const PartitionDirectMapExtent* extent = | |
| PartitionDirectMapExtent::FromPage(page); | |
| size_t unmap_size = extent->map_size; | |
| // Maintain the doubly-linked list of all direct mappings. | |
| if (extent->prev_extent) { | |
| DCHECK(extent->prev_extent->next_extent == extent); | |
| extent->prev_extent->next_extent = extent->next_extent; | |
| } else { | |
| root->direct_map_list = extent->next_extent; | |
| } | |
| if (extent->next_extent) { | |
| DCHECK(extent->next_extent->prev_extent == extent); | |
| extent->next_extent->prev_extent = extent->prev_extent; | |
| } | |
| // Add on the size of the trailing guard page and preceeding partition | |
| // page. | |
| unmap_size += PartitionPageSize() + SystemPageSize(); | |
| size_t uncommitted_page_size = page->bucket->slot_size + SystemPageSize(); | |
| root->DecreaseCommittedPages(uncommitted_page_size); | |
| DCHECK(root->total_size_of_direct_mapped_pages >= uncommitted_page_size); | |
| root->total_size_of_direct_mapped_pages -= uncommitted_page_size; | |
| DCHECK(!(unmap_size & PageAllocationGranularityOffsetMask())); | |
| char* ptr = reinterpret_cast<char*>(PartitionPage::ToPointer(page)); | |
| // Account for the mapping starting a partition page before the actual | |
| // allocation address. | |
| ptr -= PartitionPageSize(); | |
| return {ptr, unmap_size}; | |
| } | |
| ALWAYS_INLINE void PartitionRegisterEmptyPage(PartitionPage* page) { | |
| DCHECK(page->is_empty()); | |
| PartitionRootBase* root = PartitionRootBase::FromPage(page); | |
| // If the page is already registered as empty, give it another life. | |
| if (page->empty_cache_index != -1) { | |
| DCHECK(page->empty_cache_index >= 0); | |
| DCHECK(static_cast<unsigned>(page->empty_cache_index) < kMaxFreeableSpans); | |
| DCHECK(root->global_empty_page_ring[page->empty_cache_index] == page); | |
| root->global_empty_page_ring[page->empty_cache_index] = nullptr; | |
| } | |
| int16_t current_index = root->global_empty_page_ring_index; | |
| PartitionPage* page_to_decommit = root->global_empty_page_ring[current_index]; | |
| // The page might well have been re-activated, filled up, etc. before we get | |
| // around to looking at it here. | |
| if (page_to_decommit) | |
| page_to_decommit->DecommitIfPossible(root); | |
| // We put the empty slot span on our global list of "pages that were once | |
| // empty". thus providing it a bit of breathing room to get re-used before | |
| // we really free it. This improves performance, particularly on Mac OS X | |
| // which has subpar memory management performance. | |
| root->global_empty_page_ring[current_index] = page; | |
| page->empty_cache_index = current_index; | |
| ++current_index; | |
| if (current_index == kMaxFreeableSpans) | |
| current_index = 0; | |
| root->global_empty_page_ring_index = current_index; | |
| } | |
| } // namespace | |
| // static | |
| PartitionPage PartitionPage::sentinel_page_; | |
| PartitionPage* PartitionPage::get_sentinel_page() { | |
| return &sentinel_page_; | |
| } | |
| DeferredUnmap PartitionPage::FreeSlowPath() { | |
| DCHECK(this != get_sentinel_page()); | |
| if (LIKELY(num_allocated_slots == 0)) { | |
| // Page became fully unused. | |
| if (UNLIKELY(bucket->is_direct_mapped())) { | |
| return PartitionDirectUnmap(this); | |
| } | |
| // If it's the current active page, change it. We bounce the page to | |
| // the empty list as a force towards defragmentation. | |
| if (LIKELY(this == bucket->active_pages_head)) | |
| bucket->SetNewActivePage(); | |
| DCHECK(bucket->active_pages_head != this); | |
| set_raw_size(0); | |
| DCHECK(!get_raw_size()); | |
| PartitionRegisterEmptyPage(this); | |
| } else { | |
| DCHECK(!bucket->is_direct_mapped()); | |
| // Ensure that the page is full. That's the only valid case if we | |
| // arrive here. | |
| DCHECK(num_allocated_slots < 0); | |
| // A transition of num_allocated_slots from 0 to -1 is not legal, and | |
| // likely indicates a double-free. | |
| CHECK(num_allocated_slots != -1); | |
| num_allocated_slots = -num_allocated_slots - 2; | |
| DCHECK(num_allocated_slots == bucket->get_slots_per_span() - 1); | |
| // Fully used page became partially used. It must be put back on the | |
| // non-full page list. Also make it the current page to increase the | |
| // chances of it being filled up again. The old current page will be | |
| // the next page. | |
| DCHECK(!next_page); | |
| if (LIKELY(bucket->active_pages_head != get_sentinel_page())) | |
| next_page = bucket->active_pages_head; | |
| bucket->active_pages_head = this; | |
| --bucket->num_full_pages; | |
| // Special case: for a partition page with just a single slot, it may | |
| // now be empty and we want to run it through the empty logic. | |
| if (UNLIKELY(num_allocated_slots == 0)) | |
| return FreeSlowPath(); | |
| } | |
| return {}; | |
| } | |
| void PartitionPage::Decommit(PartitionRootBase* root) { | |
| DCHECK(is_empty()); | |
| DCHECK(!bucket->is_direct_mapped()); | |
| void* addr = PartitionPage::ToPointer(this); | |
| root->DecommitSystemPages(addr, bucket->get_bytes_per_span()); | |
| // We actually leave the decommitted page in the active list. We'll sweep | |
| // it on to the decommitted page list when we next walk the active page | |
| // list. | |
| // Pulling this trick enables us to use a singly-linked page list for all | |
| // cases, which is critical in keeping the page metadata structure down to | |
| // 32 bytes in size. | |
| freelist_head = nullptr; | |
| num_unprovisioned_slots = 0; | |
| DCHECK(is_decommitted()); | |
| } | |
| void PartitionPage::DecommitIfPossible(PartitionRootBase* root) { | |
| DCHECK(empty_cache_index >= 0); | |
| DCHECK(static_cast<unsigned>(empty_cache_index) < kMaxFreeableSpans); | |
| DCHECK(this == root->global_empty_page_ring[empty_cache_index]); | |
| empty_cache_index = -1; | |
| if (is_empty()) | |
| Decommit(root); | |
| } | |
| void DeferredUnmap::Unmap() { | |
| FreePages(ptr, size); | |
| } | |
| } // namespace internal | |
| } // namespace base | |
| } // namespace pdfium |