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