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 #ifndef THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ROOT_BASE_H_
6 #define THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ROOT_BASE_H_
7
8 #include "build/build_config.h"
9 #include "third_party/base/allocator/partition_allocator/page_allocator.h"
10 #include "third_party/base/allocator/partition_allocator/partition_alloc_constants.h"
11 #include "third_party/base/allocator/partition_allocator/partition_bucket.h"
12 #include "third_party/base/allocator/partition_allocator/partition_direct_map_extent.h"
13 #include "third_party/base/allocator/partition_allocator/partition_page.h"
14
15 namespace pdfium {
16 namespace base {
17 namespace internal {
18
19 struct PartitionPage;
20 struct PartitionRootBase;
21
22 // An "extent" is a span of consecutive superpages. We link to the partition's
23 // next extent (if there is one) to the very start of a superpage's metadata
24 // area.
25 struct PartitionSuperPageExtentEntry {
26 PartitionRootBase* root;
27 char* super_page_base;
28 char* super_pages_end;
29 PartitionSuperPageExtentEntry* next;
30 };
31 static_assert(
32 sizeof(PartitionSuperPageExtentEntry) <= kPageMetadataSize,
33 "PartitionSuperPageExtentEntry must be able to fit in a metadata slot");
34
35 struct BASE_EXPORT PartitionRootBase {
36 PartitionRootBase();
37 virtual ~PartitionRootBase();
38 size_t total_size_of_committed_pages = 0;
39 size_t total_size_of_super_pages = 0;
40 size_t total_size_of_direct_mapped_pages = 0;
41 // Invariant: total_size_of_committed_pages <=
42 // total_size_of_super_pages +
43 // total_size_of_direct_mapped_pages.
44 unsigned num_buckets = 0;
45 unsigned max_allocation = 0;
46 bool initialized = false;
47 char* next_super_page = nullptr;
48 char* next_partition_page = nullptr;
49 char* next_partition_page_end = nullptr;
50 PartitionSuperPageExtentEntry* current_extent = nullptr;
51 PartitionSuperPageExtentEntry* first_extent = nullptr;
52 PartitionDirectMapExtent* direct_map_list = nullptr;
53 PartitionPage* global_empty_page_ring[kMaxFreeableSpans] = {};
54 int16_t global_empty_page_ring_index = 0;
55 uintptr_t inverted_self = 0;
56
57 // Public API
58
59 // Allocates out of the given bucket. Properly, this function should probably
60 // be in PartitionBucket, but because the implementation needs to be inlined
61 // for performance, and because it needs to inspect PartitionPage,
62 // it becomes impossible to have it in PartitionBucket as this causes a
63 // cyclical dependency on PartitionPage function implementations.
64 //
65 // Moving it a layer lower couples PartitionRootBase and PartitionBucket, but
66 // preserves the layering of the includes.
67 //
68 // Note the matching Free() functions are in PartitionPage.
69 ALWAYS_INLINE void* AllocFromBucket(PartitionBucket* bucket,
70 int flags,
71 size_t size);
72
73 ALWAYS_INLINE static bool IsValidPage(PartitionPage* page);
74 ALWAYS_INLINE static PartitionRootBase* FromPage(PartitionPage* page);
75
76 // gOomHandlingFunction is invoked when PartitionAlloc hits OutOfMemory.
77 static void (*gOomHandlingFunction)();
78 NOINLINE void OutOfMemory();
79
80 ALWAYS_INLINE void IncreaseCommittedPages(size_t len);
81 ALWAYS_INLINE void DecreaseCommittedPages(size_t len);
82 ALWAYS_INLINE void DecommitSystemPages(void* address, size_t length);
83 ALWAYS_INLINE void RecommitSystemPages(void* address, size_t length);
84
85 // Frees memory from this partition, if possible, by decommitting pages.
86 // |flags| is an OR of base::PartitionPurgeFlags.
87 virtual void PurgeMemory(int flags) = 0;
88 void DecommitEmptyPages();
89 };
90
AllocFromBucket(PartitionBucket * bucket,int flags,size_t size)91 ALWAYS_INLINE void* PartitionRootBase::AllocFromBucket(PartitionBucket* bucket,
92 int flags,
93 size_t size) {
94 bool zero_fill = flags & PartitionAllocZeroFill;
95 bool is_already_zeroed = false;
96
97 PartitionPage* page = bucket->active_pages_head;
98 // Check that this page is neither full nor freed.
99 DCHECK(page->num_allocated_slots >= 0);
100 void* ret = page->freelist_head;
101 if (LIKELY(ret != 0)) {
102 // If these DCHECKs fire, you probably corrupted memory. TODO(palmer): See
103 // if we can afford to make these CHECKs.
104 DCHECK(PartitionRootBase::IsValidPage(page));
105
106 // All large allocations must go through the slow path to correctly update
107 // the size metadata.
108 DCHECK(page->get_raw_size() == 0);
109 internal::PartitionFreelistEntry* new_head =
110 internal::EncodedPartitionFreelistEntry::Decode(
111 page->freelist_head->next);
112 page->freelist_head = new_head;
113 page->num_allocated_slots++;
114 } else {
115 ret = bucket->SlowPathAlloc(this, flags, size, &is_already_zeroed);
116 // TODO(palmer): See if we can afford to make this a CHECK.
117 DCHECK(!ret ||
118 PartitionRootBase::IsValidPage(PartitionPage::FromPointer(ret)));
119 }
120
121 #if DCHECK_IS_ON()
122 if (!ret) {
123 return nullptr;
124 }
125
126 page = PartitionPage::FromPointer(ret);
127 // TODO(ajwong): Can |page->bucket| ever not be |this|? If not, can this just
128 // be bucket->slot_size?
129 size_t new_slot_size = page->bucket->slot_size;
130 size_t raw_size = page->get_raw_size();
131 if (raw_size) {
132 DCHECK(raw_size == size);
133 new_slot_size = raw_size;
134 }
135 size_t no_cookie_size = PartitionCookieSizeAdjustSubtract(new_slot_size);
136 char* char_ret = static_cast<char*>(ret);
137 // The value given to the application is actually just after the cookie.
138 ret = char_ret + kCookieSize;
139
140 // Fill the region kUninitializedByte or 0, and surround it with 2 cookies.
141 PartitionCookieWriteValue(char_ret);
142 if (!zero_fill) {
143 memset(ret, kUninitializedByte, no_cookie_size);
144 } else if (!is_already_zeroed) {
145 memset(ret, 0, no_cookie_size);
146 }
147 PartitionCookieWriteValue(char_ret + kCookieSize + no_cookie_size);
148 #else
149 if (ret && zero_fill && !is_already_zeroed) {
150 memset(ret, 0, size);
151 }
152 #endif
153
154 return ret;
155 }
156
IsValidPage(PartitionPage * page)157 ALWAYS_INLINE bool PartitionRootBase::IsValidPage(PartitionPage* page) {
158 PartitionRootBase* root = PartitionRootBase::FromPage(page);
159 return root->inverted_self == ~reinterpret_cast<uintptr_t>(root);
160 }
161
FromPage(PartitionPage * page)162 ALWAYS_INLINE PartitionRootBase* PartitionRootBase::FromPage(
163 PartitionPage* page) {
164 PartitionSuperPageExtentEntry* extent_entry =
165 reinterpret_cast<PartitionSuperPageExtentEntry*>(
166 reinterpret_cast<uintptr_t>(page) & kSystemPageBaseMask);
167 return extent_entry->root;
168 }
169
IncreaseCommittedPages(size_t len)170 ALWAYS_INLINE void PartitionRootBase::IncreaseCommittedPages(size_t len) {
171 total_size_of_committed_pages += len;
172 DCHECK(total_size_of_committed_pages <=
173 total_size_of_super_pages + total_size_of_direct_mapped_pages);
174 }
175
DecreaseCommittedPages(size_t len)176 ALWAYS_INLINE void PartitionRootBase::DecreaseCommittedPages(size_t len) {
177 total_size_of_committed_pages -= len;
178 DCHECK(total_size_of_committed_pages <=
179 total_size_of_super_pages + total_size_of_direct_mapped_pages);
180 }
181
DecommitSystemPages(void * address,size_t length)182 ALWAYS_INLINE void PartitionRootBase::DecommitSystemPages(void* address,
183 size_t length) {
184 ::pdfium::base::DecommitSystemPages(address, length);
185 DecreaseCommittedPages(length);
186 }
187
RecommitSystemPages(void * address,size_t length)188 ALWAYS_INLINE void PartitionRootBase::RecommitSystemPages(void* address,
189 size_t length) {
190 CHECK(::pdfium::base::RecommitSystemPages(address, length, PageReadWrite));
191 IncreaseCommittedPages(length);
192 }
193
194 } // namespace internal
195 } // namespace base
196 } // namespace pdfium
197
198 #endif // THIRD_PARTY_BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ROOT_BASE_H_
199