|  | /* | 
|  | * Copyright (C) 2014 Google Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions are | 
|  | * met: | 
|  | * | 
|  | *     * Redistributions of source code must retain the above copyright | 
|  | * notice, this list of conditions and the following disclaimer. | 
|  | *     * Redistributions in binary form must reproduce the above | 
|  | * copyright notice, this list of conditions and the following disclaimer | 
|  | * in the documentation and/or other materials provided with the | 
|  | * distribution. | 
|  | *     * Neither the name of Google Inc. nor the names of its | 
|  | * contributors may be used to endorse or promote products derived from | 
|  | * this software without specific prior written permission. | 
|  | * | 
|  | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
|  | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
|  | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
|  | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
|  | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
|  | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
|  | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
|  | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
|  | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include "sky/engine/config.h" | 
|  | #include "sky/engine/platform/PurgeableVector.h" | 
|  |  | 
|  | #include "sky/engine/public/platform/Platform.h" | 
|  | #include "sky/engine/public/platform/WebDiscardableMemory.h" | 
|  | #include "sky/engine/wtf/Assertions.h" | 
|  | #include "sky/engine/wtf/OwnPtr.h" | 
|  | #include "sky/engine/wtf/PassOwnPtr.h" | 
|  |  | 
|  | #include <cstring> | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | // WebDiscardableMemory allocations are expensive and page-grained. We only use | 
|  | // them when there's a reasonable amount of memory to be saved by the OS | 
|  | // discarding the memory. | 
|  | static const size_t minimumDiscardableAllocationSize = 4 * 4096; | 
|  |  | 
|  | PurgeableVector::PurgeableVector(PurgeableOption purgeable) | 
|  | : m_discardableCapacity(0) | 
|  | , m_discardableSize(0) | 
|  | , m_isPurgeable(purgeable == Purgeable) | 
|  | , m_locksCount(1) // The buffer is locked at creation. | 
|  | { | 
|  | } | 
|  |  | 
|  | PurgeableVector::~PurgeableVector() | 
|  | { | 
|  | } | 
|  |  | 
|  | void PurgeableVector::reserveCapacity(size_t capacity) | 
|  | { | 
|  | ASSERT(isLocked()); | 
|  |  | 
|  | if (m_isPurgeable) { | 
|  | if (reservePurgeableCapacity(capacity, UseExactCapacity)) | 
|  | return; | 
|  | // Fallback to non-purgeable buffer allocation in case discardable memory allocation failed. | 
|  | } | 
|  |  | 
|  | if (!m_vector.capacity()) { | 
|  | // Using reserveInitialCapacity() on the underlying vector ensures that the vector uses the | 
|  | // exact specified capacity to avoid consuming too much memory for small resources. | 
|  | m_vector.reserveInitialCapacity(capacity); | 
|  | } else { | 
|  | m_vector.reserveCapacity(capacity); | 
|  | } | 
|  |  | 
|  | moveDataFromDiscardableToVector(); | 
|  | } | 
|  |  | 
|  | void PurgeableVector::moveDataFromDiscardableToVector() | 
|  | { | 
|  | if (m_discardable) { | 
|  | m_vector.append(static_cast<const char*>(m_discardable->data()), m_discardableSize); | 
|  | clearDiscardable(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PurgeableVector::clearDiscardable() | 
|  | { | 
|  | m_discardable.clear(); | 
|  | m_discardableCapacity = 0; | 
|  | m_discardableSize = 0; | 
|  | } | 
|  |  | 
|  | void PurgeableVector::append(const char* data, size_t length) | 
|  | { | 
|  | ASSERT(isLocked()); | 
|  |  | 
|  | if (!m_isPurgeable) { | 
|  | m_vector.append(data, length); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const size_t currentSize = m_discardable ? m_discardableSize : m_vector.size(); | 
|  | const size_t newBufferSize = currentSize + length; | 
|  |  | 
|  | if (!reservePurgeableCapacity(newBufferSize, UseExponentialGrowth)) { | 
|  | moveDataFromDiscardableToVector(); | 
|  | m_vector.append(data, length); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ASSERT(m_discardableSize + length <= m_discardableCapacity); | 
|  | memcpy(static_cast<char*>(m_discardable->data()) + m_discardableSize, data, length); | 
|  | m_discardableSize += length; | 
|  | } | 
|  |  | 
|  | void PurgeableVector::grow(size_t newSize) | 
|  | { | 
|  | ASSERT(newSize >= size()); | 
|  |  | 
|  | if (m_isPurgeable) { | 
|  | if (reservePurgeableCapacity(newSize, UseExponentialGrowth)) { | 
|  | m_discardableSize = newSize; | 
|  | return; | 
|  | } | 
|  | moveDataFromDiscardableToVector(); | 
|  | } | 
|  |  | 
|  | m_vector.resize(newSize); | 
|  | } | 
|  |  | 
|  | void PurgeableVector::clear() | 
|  | { | 
|  | clearDiscardable(); | 
|  | m_vector.clear(); | 
|  | } | 
|  |  | 
|  | char* PurgeableVector::data() | 
|  | { | 
|  | ASSERT(isLocked()); | 
|  | return m_discardable ? static_cast<char*>(m_discardable->data()) : m_vector.data(); | 
|  | } | 
|  |  | 
|  | size_t PurgeableVector::size() const | 
|  | { | 
|  | return m_discardable ? m_discardableSize : m_vector.size(); | 
|  | } | 
|  |  | 
|  | void PurgeableVector::adopt(Vector<char>& other) | 
|  | { | 
|  | if (size() > 0) | 
|  | clear(); | 
|  |  | 
|  | if (!m_isPurgeable) { | 
|  | m_vector.swap(other); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (other.isEmpty()) | 
|  | return; | 
|  |  | 
|  | append(other.data(), other.size()); | 
|  | other.clear(); | 
|  | } | 
|  |  | 
|  | bool PurgeableVector::lock() | 
|  | { | 
|  | ++m_locksCount; | 
|  | if (m_locksCount > 1) | 
|  | return true; | 
|  |  | 
|  | ASSERT(m_locksCount == 1); | 
|  | if (!m_discardable) | 
|  | return true; | 
|  |  | 
|  | return m_discardable->lock(); | 
|  | } | 
|  |  | 
|  | void PurgeableVector::unlock() | 
|  | { | 
|  | ASSERT(isLocked()); | 
|  | --m_locksCount; | 
|  | if (m_locksCount > 0) | 
|  | return; | 
|  |  | 
|  | if (!m_vector.isEmpty()) { | 
|  | ASSERT(!m_discardable); | 
|  | m_isPurgeable = true; | 
|  | if (!reservePurgeableCapacity(m_vector.size(), UseExactCapacity)) | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (m_discardable) | 
|  | m_discardable->unlock(); | 
|  | } | 
|  |  | 
|  | bool PurgeableVector::isLocked() const | 
|  | { | 
|  | ASSERT(m_locksCount >= 0); | 
|  | return m_locksCount > 0; | 
|  | } | 
|  |  | 
|  | bool PurgeableVector::reservePurgeableCapacity(size_t capacity, PurgeableAllocationStrategy allocationStrategy) | 
|  | { | 
|  | ASSERT(m_isPurgeable); | 
|  |  | 
|  | if (m_discardable && m_discardableCapacity >= capacity) { | 
|  | ASSERT(!m_vector.capacity()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (capacity < minimumDiscardableAllocationSize) | 
|  | return false; | 
|  |  | 
|  | if (allocationStrategy == UseExponentialGrowth) | 
|  | capacity = adjustPurgeableCapacity(capacity); | 
|  |  | 
|  | OwnPtr<blink::WebDiscardableMemory> discardable = adoptPtr( | 
|  | blink::Platform::current()->allocateAndLockDiscardableMemory(capacity)); | 
|  | if (!discardable) { | 
|  | // Discardable memory is not supported. | 
|  | m_isPurgeable = false; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | m_discardableCapacity = capacity; | 
|  | // Copy the data that was either in the previous purgeable buffer or in the vector to the new | 
|  | // purgeable buffer. | 
|  | if (m_discardable) { | 
|  | memcpy(discardable->data(), m_discardable->data(), m_discardableSize); | 
|  | } else { | 
|  | memcpy(discardable->data(), m_vector.data(), m_vector.size()); | 
|  | m_discardableSize = m_vector.size(); | 
|  | m_vector.clear(); | 
|  | } | 
|  |  | 
|  | m_discardable.swap(discardable); | 
|  | ASSERT(!m_vector.capacity()); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | size_t PurgeableVector::adjustPurgeableCapacity(size_t capacity) const | 
|  | { | 
|  | ASSERT(capacity >= minimumDiscardableAllocationSize); | 
|  |  | 
|  | const float growthFactor = 1.5; | 
|  | size_t newCapacity = std::max(capacity, static_cast<size_t>(m_discardableCapacity * growthFactor)); | 
|  |  | 
|  | // Discardable memory has page-granularity so align to the next page here to minimize | 
|  | // fragmentation. | 
|  | // Since the page size is only used below to minimize fragmentation it's still safe to use it | 
|  | // even if it gets out of sync (e.g. due to the use of huge pages). | 
|  | const size_t kPageSize = 4096; | 
|  | newCapacity = (newCapacity + kPageSize - 1) & ~(kPageSize - 1); | 
|  |  | 
|  | return std::max(capacity, newCapacity); // Overflow check. | 
|  | } | 
|  |  | 
|  | } // namespace blink |