1 // Copyright (c) 2011 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 COURGETTE_MEMORY_ALLOCATOR_H_ 6 #define COURGETTE_MEMORY_ALLOCATOR_H_ 7 8 #include <memory> 9 10 #include "base/basictypes.h" 11 #include "base/files/file.h" 12 #include "base/files/file_path.h" 13 #include "base/logging.h" 14 15 #ifndef NDEBUG 16 17 // A helper class to track down call sites that are not handling error cases. 18 template<class T> 19 class CheckReturnValue { 20 public: 21 // Not marked explicit on purpose. CheckReturnValue(T value)22 CheckReturnValue(T value) : value_(value), checked_(false) { // NOLINT 23 } CheckReturnValue(const CheckReturnValue & other)24 CheckReturnValue(const CheckReturnValue& other) 25 : value_(other.value_), checked_(other.checked_) { 26 other.checked_ = true; 27 } 28 29 CheckReturnValue& operator=(const CheckReturnValue& other) { 30 if (this != &other) { 31 DCHECK(checked_); 32 value_ = other.value_; 33 checked_ = other.checked_; 34 other.checked_ = true; 35 } 36 } 37 ~CheckReturnValue()38 ~CheckReturnValue() { 39 DCHECK(checked_); 40 } 41 42 operator const T&() const { 43 checked_ = true; 44 return value_; 45 } 46 47 private: 48 T value_; 49 mutable bool checked_; 50 }; 51 typedef CheckReturnValue<bool> CheckBool; 52 #else 53 typedef bool CheckBool; 54 #endif 55 56 namespace courgette { 57 58 #if defined(OS_WIN) 59 60 // Manages a read/write virtual mapping of a physical file. 61 class FileMapping { 62 public: 63 FileMapping(); 64 ~FileMapping(); 65 66 // Map a file from beginning to |size|. 67 bool Create(HANDLE file, size_t size); 68 void Close(); 69 70 // Returns true iff a mapping has been created. 71 bool valid() const; 72 73 // Returns a writable pointer to the beginning of the memory mapped file. 74 // If Create has not been called successfully, return value is NULL. 75 void* view() const; 76 77 protected: 78 bool InitializeView(size_t size); 79 80 HANDLE mapping_; 81 void* view_; 82 }; 83 84 // Manages a temporary file and a memory mapping of the temporary file. 85 // The memory that this class manages holds a pointer back to the TempMapping 86 // object itself, so that given a memory pointer allocated by this class, 87 // you can get a pointer to the TempMapping instance that owns that memory. 88 class TempMapping { 89 public: 90 TempMapping(); 91 ~TempMapping(); 92 93 // Creates a temporary file of size |size| and maps it into the current 94 // process's address space. 95 bool Initialize(size_t size); 96 97 // Returns a writable pointer to the reserved memory. 98 void* memory() const; 99 100 // Returns true if the mapping is valid and memory is available. 101 bool valid() const; 102 103 // Returns a pointer to the TempMapping instance that allocated the |mem| 104 // block of memory. It's the callers responsibility to make sure that 105 // the memory block was allocated by the TempMapping class. 106 static TempMapping* GetMappingFromPtr(void* mem); 107 108 protected: 109 base::File file_; 110 FileMapping mapping_; 111 }; 112 113 // A memory allocator class that allocates memory either from the heap or via a 114 // temporary file. The interface is STL inspired but the class does not throw 115 // STL exceptions on allocation failure. Instead it returns NULL. 116 // A file allocation will be made if either the requested memory size exceeds 117 // |kMaxHeapAllocationSize| or if a heap allocation fails. 118 // Allocating the memory as a mapping of a temporary file solves the problem 119 // that there might not be enough physical memory and pagefile to support the 120 // allocation. This can happen because these resources are too small, or 121 // already committed to other processes. Provided there is enough disk, the 122 // temporary file acts like a pagefile that other processes can't access. 123 template<class T> 124 class MemoryAllocator { 125 public: 126 typedef T value_type; 127 typedef value_type* pointer; 128 typedef value_type& reference; 129 typedef const value_type* const_pointer; 130 typedef const value_type& const_reference; 131 typedef size_t size_type; 132 typedef ptrdiff_t difference_type; 133 134 // Each allocation is tagged with a single byte so that we know how to 135 // deallocate it. 136 enum AllocationType { 137 HEAP_ALLOCATION, 138 FILE_ALLOCATION, 139 }; 140 141 // 5MB is the maximum heap allocation size that we'll attempt. 142 // When applying a patch for Chrome 10.X we found that at this 143 // threshold there were 17 allocations higher than this threshold 144 // (largest at 136MB) 10 allocations just below the threshold and 6362 145 // smaller allocations. 146 static const size_t kMaxHeapAllocationSize = 1024 * 1024 * 5; 147 148 template<class OtherT> 149 struct rebind { 150 // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT> 151 typedef MemoryAllocator<OtherT> other; 152 }; 153 _THROW0()154 MemoryAllocator() _THROW0() { 155 } 156 157 // We can't use an explicit constructor here, as dictated by our style guide. 158 // The implementation of basic_string in Visual Studio 2010 prevents this. _THROW0()159 MemoryAllocator(const MemoryAllocator<T>& other) _THROW0() { // NOLINT 160 } 161 162 template<class OtherT> MemoryAllocator(const MemoryAllocator<OtherT> & other)163 MemoryAllocator(const MemoryAllocator<OtherT>& other) _THROW0() { // NOLINT 164 } 165 ~MemoryAllocator()166 ~MemoryAllocator() { 167 } 168 deallocate(pointer ptr,size_type size)169 void deallocate(pointer ptr, size_type size) { 170 uint8* mem = reinterpret_cast<uint8*>(ptr); 171 mem -= sizeof(T); 172 if (mem[0] == HEAP_ALLOCATION) { 173 delete [] mem; 174 } else { 175 DCHECK_EQ(static_cast<uint8>(FILE_ALLOCATION), mem[0]); 176 TempMapping* mapping = TempMapping::GetMappingFromPtr(mem); 177 delete mapping; 178 } 179 } 180 allocate(size_type count)181 pointer allocate(size_type count) { 182 // We use the first byte of each allocation to mark the allocation type. 183 // However, so that the allocation is properly aligned, we allocate an 184 // extra element and then use the first byte of the first element 185 // to mark the allocation type. 186 count++; 187 188 if (count > max_size()) 189 return NULL; 190 191 size_type bytes = count * sizeof(T); 192 uint8* mem = NULL; 193 194 // First see if we can do this allocation on the heap. 195 if (count < kMaxHeapAllocationSize) 196 mem = new(std::nothrow) uint8[bytes]; 197 if (mem != NULL) { 198 mem[0] = static_cast<uint8>(HEAP_ALLOCATION); 199 } else { 200 // If either the heap allocation failed or the request exceeds the 201 // max heap allocation threshold, we back the allocation with a temp file. 202 TempMapping* mapping = new(std::nothrow) TempMapping(); 203 if (mapping && mapping->Initialize(bytes)) { 204 mem = reinterpret_cast<uint8*>(mapping->memory()); 205 mem[0] = static_cast<uint8>(FILE_ALLOCATION); 206 } 207 } 208 return mem ? reinterpret_cast<pointer>(mem + sizeof(T)) : NULL; 209 } 210 allocate(size_type count,const void * hint)211 pointer allocate(size_type count, const void* hint) { 212 return allocate(count); 213 } 214 construct(pointer ptr,const T & value)215 void construct(pointer ptr, const T& value) { 216 ::new(ptr) T(value); 217 } 218 destroy(pointer ptr)219 void destroy(pointer ptr) { 220 ptr->~T(); 221 } 222 max_size()223 size_type max_size() const _THROW0() { 224 size_type count = static_cast<size_type>(-1) / sizeof(T); 225 return (0 < count ? count : 1); 226 } 227 }; 228 229 #else // OS_WIN 230 231 // On Mac, Linux, we use a bare bones implementation that only does 232 // heap allocations. 233 template<class T> 234 class MemoryAllocator { 235 public: 236 typedef T value_type; 237 typedef value_type* pointer; 238 typedef value_type& reference; 239 typedef const value_type* const_pointer; 240 typedef const value_type& const_reference; 241 typedef size_t size_type; 242 typedef ptrdiff_t difference_type; 243 244 template<class OtherT> 245 struct rebind { 246 // convert a MemoryAllocator<T> to a MemoryAllocator<OtherT> 247 typedef MemoryAllocator<OtherT> other; 248 }; 249 250 MemoryAllocator() { 251 } 252 253 explicit MemoryAllocator(const MemoryAllocator<T>& other) { 254 } 255 256 template<class OtherT> 257 explicit MemoryAllocator(const MemoryAllocator<OtherT>& other) { 258 } 259 260 ~MemoryAllocator() { 261 } 262 263 void deallocate(pointer ptr, size_type size) { 264 delete [] ptr; 265 } 266 267 pointer allocate(size_type count) { 268 if (count > max_size()) 269 return NULL; 270 return reinterpret_cast<pointer>( 271 new(std::nothrow) uint8[count * sizeof(T)]); 272 } 273 274 pointer allocate(size_type count, const void* hint) { 275 return allocate(count); 276 } 277 278 void construct(pointer ptr, const T& value) { 279 ::new(ptr) T(value); 280 } 281 282 void destroy(pointer ptr) { 283 ptr->~T(); 284 } 285 286 size_type max_size() const { 287 size_type count = static_cast<size_type>(-1) / sizeof(T); 288 return (0 < count ? count : 1); 289 } 290 }; 291 292 #endif // OS_WIN 293 294 // Manages a growable buffer. The buffer allocation is done by the 295 // MemoryAllocator class. This class will not throw exceptions so call sites 296 // must be prepared to handle memory allocation failures. 297 // The interface is STL inspired to avoid having to make too many changes 298 // to code that previously was using STL. 299 template<typename T, class Allocator = MemoryAllocator<T> > 300 class NoThrowBuffer { 301 public: 302 typedef T value_type; 303 static const size_t kAllocationFailure = 0xffffffff; 304 static const size_t kStartSize = sizeof(T) > 0x100 ? 1 : 0x100 / sizeof(T); 305 NoThrowBuffer()306 NoThrowBuffer() : buffer_(NULL), size_(0), alloc_size_(0) { 307 } 308 ~NoThrowBuffer()309 ~NoThrowBuffer() { 310 clear(); 311 } 312 clear()313 void clear() { 314 if (buffer_) { 315 alloc_.deallocate(buffer_, alloc_size_); 316 buffer_ = NULL; 317 size_ = 0; 318 alloc_size_ = 0; 319 } 320 } 321 empty()322 bool empty() const { 323 return size_ == 0; 324 } 325 reserve(size_t size)326 CheckBool reserve(size_t size) WARN_UNUSED_RESULT { 327 if (failed()) 328 return false; 329 330 if (size <= alloc_size_) 331 return true; 332 333 if (size < kStartSize) 334 size = kStartSize; 335 336 // Use a size 1% higher than requested. In practice, this makes Courgette as 337 // much as 5x faster on typical Chrome update payloads as a lot of future 338 // reserve() calls will become no-ops instead of costly resizes that copy 339 // all the data. Note that doing this here instead of outside the function 340 // is more efficient, since it's after the no-op early return checks above. 341 size *= 1.01; 342 T* new_buffer = alloc_.allocate(size); 343 if (!new_buffer) { 344 clear(); 345 alloc_size_ = kAllocationFailure; 346 } else { 347 if (buffer_) { 348 memcpy(new_buffer, buffer_, size_ * sizeof(T)); 349 alloc_.deallocate(buffer_, alloc_size_); 350 } 351 buffer_ = new_buffer; 352 alloc_size_ = size; 353 } 354 355 return !failed(); 356 } 357 append(const T * data,size_t size)358 CheckBool append(const T* data, size_t size) WARN_UNUSED_RESULT { 359 if (failed()) 360 return false; 361 362 if (size > alloc_.max_size() - size_) 363 return false; 364 365 if (!size) 366 return true; 367 368 if ((alloc_size_ - size_) < size) { 369 const size_t max_size = alloc_.max_size(); 370 size_t new_size = alloc_size_ ? alloc_size_ : kStartSize; 371 while (new_size < size_ + size) { 372 if (new_size < max_size - new_size) { 373 new_size *= 2; 374 } else { 375 new_size = max_size; 376 } 377 } 378 if (!reserve(new_size)) 379 return false; 380 } 381 382 memcpy(buffer_ + size_, data, size * sizeof(T)); 383 size_ += size; 384 385 return true; 386 } 387 resize(size_t size,const T & init_value)388 CheckBool resize(size_t size, const T& init_value) WARN_UNUSED_RESULT { 389 if (size > size_) { 390 if (!reserve(size)) 391 return false; 392 for (size_t i = size_; i < size; ++i) 393 buffer_[i] = init_value; 394 } else if (size < size_) { 395 // TODO(tommi): Should we allocate a new, smaller buffer? 396 // It might be faster for us to simply change the size. 397 } 398 399 size_ = size; 400 401 return true; 402 } 403 push_back(const T & item)404 CheckBool push_back(const T& item) WARN_UNUSED_RESULT { 405 return append(&item, 1); 406 } 407 back()408 const T& back() const { 409 return buffer_[size_ - 1]; 410 } 411 back()412 T& back() { 413 return buffer_[size_ - 1]; 414 } 415 begin()416 const T* begin() const { 417 if (!size_) 418 return NULL; 419 return &buffer_[0]; 420 } 421 begin()422 T* begin() { 423 if (!size_) 424 return NULL; 425 return &buffer_[0]; 426 } 427 end()428 const T* end() const { 429 if (!size_) 430 return NULL; 431 return &buffer_[size_ - 1]; 432 } 433 end()434 T* end() { 435 if (!size_) 436 return NULL; 437 return &buffer_[size_ - 1]; 438 } 439 440 const T& operator[](size_t index) const { 441 DCHECK(index < size_); 442 return buffer_[index]; 443 } 444 445 T& operator[](size_t index) { 446 DCHECK(index < size_); 447 return buffer_[index]; 448 } 449 size()450 size_t size() const { 451 return size_; 452 } 453 data()454 T* data() const { 455 return buffer_; 456 } 457 458 // Returns true if an allocation failure has ever occurred for this object. failed()459 bool failed() const { 460 return alloc_size_ == kAllocationFailure; 461 } 462 463 protected: 464 T* buffer_; 465 size_t size_; // how much of the buffer we're using. 466 size_t alloc_size_; // how much space we have allocated. 467 Allocator alloc_; 468 }; 469 470 } // namespace courgette 471 472 #endif // COURGETTE_MEMORY_ALLOCATOR_H_ 473