1 // Copyright (c) 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/base/allocator/partition_allocator/partition_page.h"
6 
7 #include "third_party/base/allocator/partition_allocator/partition_direct_map_extent.h"
8 #include "third_party/base/allocator/partition_allocator/partition_root_base.h"
9 
10 namespace pdfium {
11 namespace base {
12 namespace internal {
13 
14 namespace {
15 
PartitionDirectUnmap(PartitionPage * page)16 ALWAYS_INLINE void PartitionDirectUnmap(PartitionPage* page) {
17   PartitionRootBase* root = PartitionRootBase::FromPage(page);
18   const PartitionDirectMapExtent* extent =
19       PartitionDirectMapExtent::FromPage(page);
20   size_t unmap_size = extent->map_size;
21 
22   // Maintain the doubly-linked list of all direct mappings.
23   if (extent->prev_extent) {
24     DCHECK(extent->prev_extent->next_extent == extent);
25     extent->prev_extent->next_extent = extent->next_extent;
26   } else {
27     root->direct_map_list = extent->next_extent;
28   }
29   if (extent->next_extent) {
30     DCHECK(extent->next_extent->prev_extent == extent);
31     extent->next_extent->prev_extent = extent->prev_extent;
32   }
33 
34   // Add on the size of the trailing guard page and preceeding partition
35   // page.
36   unmap_size += kPartitionPageSize + kSystemPageSize;
37 
38   size_t uncommitted_page_size = page->bucket->slot_size + kSystemPageSize;
39   root->DecreaseCommittedPages(uncommitted_page_size);
40   DCHECK(root->total_size_of_direct_mapped_pages >= uncommitted_page_size);
41   root->total_size_of_direct_mapped_pages -= uncommitted_page_size;
42 
43   DCHECK(!(unmap_size & kPageAllocationGranularityOffsetMask));
44 
45   char* ptr = reinterpret_cast<char*>(PartitionPage::ToPointer(page));
46   // Account for the mapping starting a partition page before the actual
47   // allocation address.
48   ptr -= kPartitionPageSize;
49 
50   FreePages(ptr, unmap_size);
51 }
52 
PartitionRegisterEmptyPage(PartitionPage * page)53 ALWAYS_INLINE void PartitionRegisterEmptyPage(PartitionPage* page) {
54   DCHECK(page->is_empty());
55   PartitionRootBase* root = PartitionRootBase::FromPage(page);
56 
57   // If the page is already registered as empty, give it another life.
58   if (page->empty_cache_index != -1) {
59     DCHECK(page->empty_cache_index >= 0);
60     DCHECK(static_cast<unsigned>(page->empty_cache_index) < kMaxFreeableSpans);
61     DCHECK(root->global_empty_page_ring[page->empty_cache_index] == page);
62     root->global_empty_page_ring[page->empty_cache_index] = nullptr;
63   }
64 
65   int16_t current_index = root->global_empty_page_ring_index;
66   PartitionPage* page_to_decommit = root->global_empty_page_ring[current_index];
67   // The page might well have been re-activated, filled up, etc. before we get
68   // around to looking at it here.
69   if (page_to_decommit)
70     page_to_decommit->DecommitIfPossible(root);
71 
72   // We put the empty slot span on our global list of "pages that were once
73   // empty". thus providing it a bit of breathing room to get re-used before
74   // we really free it. This improves performance, particularly on Mac OS X
75   // which has subpar memory management performance.
76   root->global_empty_page_ring[current_index] = page;
77   page->empty_cache_index = current_index;
78   ++current_index;
79   if (current_index == kMaxFreeableSpans)
80     current_index = 0;
81   root->global_empty_page_ring_index = current_index;
82 }
83 
84 }  // namespace
85 
86 // static
87 PartitionPage PartitionPage::sentinel_page_;
88 
get_sentinel_page()89 PartitionPage* PartitionPage::get_sentinel_page() {
90   return &sentinel_page_;
91 }
92 
FreeSlowPath()93 void PartitionPage::FreeSlowPath() {
94   DCHECK(this != get_sentinel_page());
95   if (LIKELY(num_allocated_slots == 0)) {
96     // Page became fully unused.
97     if (UNLIKELY(bucket->is_direct_mapped())) {
98       PartitionDirectUnmap(this);
99       return;
100     }
101     // If it's the current active page, change it. We bounce the page to
102     // the empty list as a force towards defragmentation.
103     if (LIKELY(this == bucket->active_pages_head))
104       bucket->SetNewActivePage();
105     DCHECK(bucket->active_pages_head != this);
106 
107     set_raw_size(0);
108     DCHECK(!get_raw_size());
109 
110     PartitionRegisterEmptyPage(this);
111   } else {
112     DCHECK(!bucket->is_direct_mapped());
113     // Ensure that the page is full. That's the only valid case if we
114     // arrive here.
115     DCHECK(num_allocated_slots < 0);
116     // A transition of num_allocated_slots from 0 to -1 is not legal, and
117     // likely indicates a double-free.
118     CHECK(num_allocated_slots != -1);
119     num_allocated_slots = -num_allocated_slots - 2;
120     DCHECK(num_allocated_slots == bucket->get_slots_per_span() - 1);
121     // Fully used page became partially used. It must be put back on the
122     // non-full page list. Also make it the current page to increase the
123     // chances of it being filled up again. The old current page will be
124     // the next page.
125     DCHECK(!next_page);
126     if (LIKELY(bucket->active_pages_head != get_sentinel_page()))
127       next_page = bucket->active_pages_head;
128     bucket->active_pages_head = this;
129     --bucket->num_full_pages;
130     // Special case: for a partition page with just a single slot, it may
131     // now be empty and we want to run it through the empty logic.
132     if (UNLIKELY(num_allocated_slots == 0))
133       FreeSlowPath();
134   }
135 }
136 
Decommit(PartitionRootBase * root)137 void PartitionPage::Decommit(PartitionRootBase* root) {
138   DCHECK(is_empty());
139   DCHECK(!bucket->is_direct_mapped());
140   void* addr = PartitionPage::ToPointer(this);
141   root->DecommitSystemPages(addr, bucket->get_bytes_per_span());
142 
143   // We actually leave the decommitted page in the active list. We'll sweep
144   // it on to the decommitted page list when we next walk the active page
145   // list.
146   // Pulling this trick enables us to use a singly-linked page list for all
147   // cases, which is critical in keeping the page metadata structure down to
148   // 32 bytes in size.
149   freelist_head = nullptr;
150   num_unprovisioned_slots = 0;
151   DCHECK(is_decommitted());
152 }
153 
DecommitIfPossible(PartitionRootBase * root)154 void PartitionPage::DecommitIfPossible(PartitionRootBase* root) {
155   DCHECK(empty_cache_index >= 0);
156   DCHECK(static_cast<unsigned>(empty_cache_index) < kMaxFreeableSpans);
157   DCHECK(this == root->global_empty_page_ring[empty_cache_index]);
158   empty_cache_index = -1;
159   if (is_empty())
160     Decommit(root);
161 }
162 
163 }  // namespace internal
164 }  // namespace base
165 }  // namespace pdfium
166