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