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