Bug 748598 - Implement VolatileBuffer for OSX, Ashmem, and Windows, r=njn,glandium

This commit is contained in:
Michael Wu 2014-01-22 08:23:27 -05:00
parent 3319661389
commit 19dba2c6c6
8 changed files with 797 additions and 0 deletions

View File

@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozalloc_VolatileBuffer_h
#define mozalloc_VolatileBuffer_h
#include "mozilla/mozalloc.h"
#include "mozilla/RefPtr.h"
#include "mozilla/MemoryReporting.h"
/* VolatileBuffer
*
* This class represents a piece of memory that can potentially be reclaimed
* by the OS when not in use. As long as there are one or more
* VolatileBufferPtrs holding on to a VolatileBuffer, the memory will remain
* available. However, when there are no VolatileBufferPtrs holding a
* VolatileBuffer, the OS can purge the pages if it wants to. The OS can make
* better decisions about what pages to purge than we can.
*
* VolatileBuffers may not always be volatile - if the allocation is too small,
* or if the OS doesn't support the feature, or if the OS doesn't want to,
* the buffer will be allocated on heap.
*
* VolatileBuffer allocations are fallible. They are intended for uses where
* one may allocate large buffers for caching data. Init() must be called
* exactly once.
*
* After getting a reference to VolatileBuffer using VolatileBufferPtr,
* WasPurged() can be used to check if the OS purged any pages in the buffer.
* The OS cannot purge a buffer immediately after a VolatileBuffer is
* initialized. At least one VolatileBufferPtr must be created before the
* buffer can be purged, so the first use of VolatileBufferPtr does not need
* to check WasPurged().
*
* When a buffer is purged, some or all of the buffer is zeroed out. This
* API cannot tell which parts of the buffer were lost.
*
* VolatileBuffer is not thread safe. Do not use VolatileBufferPtrs on
* different threads.
*/
namespace mozilla {
class MOZALLOC_EXPORT VolatileBuffer : public RefCounted<VolatileBuffer>
{
friend class VolatileBufferPtr_base;
public:
VolatileBuffer();
~VolatileBuffer();
/* aAlignment must be a multiple of the pointer size */
bool Init(size_t aSize, size_t aAlignment = sizeof(void*));
size_t HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
size_t NonHeapSizeOfExcludingThis() const;
protected:
bool Lock(void** aBuf);
void Unlock();
bool OnHeap() const;
private:
void* mBuf;
size_t mSize;
int mLockCount;
#if defined(ANDROID)
int mFd;
#elif defined(XP_DARWIN)
bool mHeap;
#elif defined(XP_WIN)
bool mHeap;
bool mFirstLock;
#endif
};
class VolatileBufferPtr_base {
public:
VolatileBufferPtr_base(VolatileBuffer* vbuf) : mVBuf(vbuf) {
mPurged = !vbuf->Lock(&mMapping);
}
~VolatileBufferPtr_base() {
mVBuf->Unlock();
}
bool WasBufferPurged() const {
return mPurged;
}
protected:
void* mMapping;
private:
RefPtr<VolatileBuffer> mVBuf;
bool mPurged;
};
template <class T>
class VolatileBufferPtr : public VolatileBufferPtr_base
{
public:
VolatileBufferPtr(VolatileBuffer* vbuf) : VolatileBufferPtr_base(vbuf) {}
operator T*() const {
return (T*) mMapping;
}
};
}; /* namespace mozilla */
#endif /* mozalloc_VolatileBuffer_h */

View File

@ -0,0 +1,137 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VolatileBuffer.h"
#include "mozilla/Assertions.h"
#include "mozilla/NullPtr.h"
#include "mozilla/mozalloc.h"
#include <fcntl.h>
#include <linux/ashmem.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#ifdef MOZ_MEMORY
#ifdef MOZ_WIDGET_ANDROID
extern "C" int __wrap_posix_memalign(void** memptr, size_t alignment, size_t size);
#else
extern "C" int posix_memalign(void** memptr, size_t alignment, size_t size);
#endif
#endif
#define MIN_VOLATILE_ALLOC_SIZE 8192
namespace mozilla {
VolatileBuffer::VolatileBuffer()
: mBuf(nullptr)
, mSize(0)
, mLockCount(0)
, mFd(-1)
{
}
bool
VolatileBuffer::Init(size_t aSize, size_t aAlignment)
{
MOZ_ASSERT(!mSize && !mBuf, "Init called twice");
MOZ_ASSERT(!(aAlignment % sizeof(void *)),
"Alignment must be multiple of pointer size");
mSize = aSize;
if (aSize < MIN_VOLATILE_ALLOC_SIZE) {
goto heap_alloc;
}
mFd = open("/" ASHMEM_NAME_DEF, O_RDWR);
if (mFd < 0) {
goto heap_alloc;
}
if (ioctl(mFd, ASHMEM_SET_SIZE, mSize) < 0) {
close(mFd);
mFd = -1;
goto heap_alloc;
}
mBuf = mmap(nullptr, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mFd, 0);
if (mBuf != MAP_FAILED) {
return true;
}
heap_alloc:
#ifdef MOZ_MEMORY
#ifdef MOZ_WIDGET_ANDROID
__wrap_posix_memalign(&mBuf, aAlignment, aSize);
#else
posix_memalign(&mBuf, aAlignment, aSize);
#endif
#else
mBuf = memalign(aAlignment, aSize);
#endif
return !!mBuf;
}
VolatileBuffer::~VolatileBuffer()
{
if (OnHeap()) {
free(mBuf);
} else {
munmap(mBuf, mSize);
close(mFd);
}
}
bool
VolatileBuffer::Lock(void** aBuf)
{
MOZ_ASSERT(mBuf, "Attempting to lock an uninitialized VolatileBuffer");
*aBuf = mBuf;
if (++mLockCount > 1 || OnHeap()) {
return true;
}
// Zero offset and zero length means we want to pin/unpin the entire thing.
struct ashmem_pin pin = { 0, 0 };
return ioctl(mFd, ASHMEM_PIN, &pin) == ASHMEM_NOT_PURGED;
}
void
VolatileBuffer::Unlock()
{
MOZ_ASSERT(mLockCount > 0, "VolatileBuffer unlocked too many times!");
if (--mLockCount || OnHeap()) {
return;
}
struct ashmem_pin pin = { 0, 0 };
ioctl(mFd, ASHMEM_UNPIN, &pin);
}
bool
VolatileBuffer::OnHeap() const
{
return mFd < 0;
}
size_t
VolatileBuffer::HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
return OnHeap() ? aMallocSizeOf(mBuf) : 0;
}
size_t
VolatileBuffer::NonHeapSizeOfExcludingThis() const
{
if (OnHeap()) {
return 0;
}
return (mSize + (PAGE_SIZE - 1)) & PAGE_MASK;
}
} // namespace mozilla

View File

@ -0,0 +1,80 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VolatileBuffer.h"
#include "mozilla/Assertions.h"
#include "mozilla/mozalloc.h"
#ifdef MOZ_MEMORY
int posix_memalign(void** memptr, size_t alignment, size_t size);
#endif
namespace mozilla {
VolatileBuffer::VolatileBuffer()
: mBuf(nullptr)
, mSize(0)
, mLockCount(0)
{
}
bool VolatileBuffer::Init(size_t aSize, size_t aAlignment)
{
MOZ_ASSERT(!mSize && !mBuf, "Init called twice");
MOZ_ASSERT(!(aAlignment % sizeof(void *)),
"Alignment must be multiple of pointer size");
mSize = aSize;
#if defined(MOZ_MEMORY)
posix_memalign(&mBuf, aAlignment, aSize);
#elif defined(HAVE_POSIX_MEMALIGN)
moz_posix_memalign(&mBuf, aAlignment, aSize);
#else
#error "No memalign implementation found"
#endif
return !!mBuf;
}
VolatileBuffer::~VolatileBuffer()
{
free(mBuf);
}
bool
VolatileBuffer::Lock(void** aBuf)
{
MOZ_ASSERT(mBuf, "Attempting to lock an uninitialized VolatileBuffer");
*aBuf = mBuf;
mLockCount++;
return true;
}
void
VolatileBuffer::Unlock()
{
mLockCount--;
MOZ_ASSERT(mLockCount >= 0, "VolatileBuffer unlocked too many times!");
}
bool
VolatileBuffer::OnHeap() const
{
return true;
}
size_t
VolatileBuffer::HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
return aMallocSizeOf(mBuf);
}
size_t
VolatileBuffer::NonHeapSizeOfExcludingThis() const
{
return 0;
}
} // namespace mozilla

View File

@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VolatileBuffer.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/mozalloc.h"
#include <mach/mach.h>
#include <sys/mman.h>
#include <unistd.h>
#define MIN_VOLATILE_ALLOC_SIZE 8192
namespace mozilla {
VolatileBuffer::VolatileBuffer()
: mBuf(nullptr)
, mSize(0)
, mLockCount(0)
, mHeap(false)
{
}
bool
VolatileBuffer::Init(size_t aSize, size_t aAlignment)
{
MOZ_ASSERT(!mSize && !mBuf, "Init called twice");
MOZ_ASSERT(!(aAlignment % sizeof(void *)),
"Alignment must be multiple of pointer size");
mSize = aSize;
kern_return_t ret = 0;
if (aSize < MIN_VOLATILE_ALLOC_SIZE) {
goto heap_alloc;
}
ret = vm_allocate(mach_task_self(),
(vm_address_t*)&mBuf,
mSize,
VM_FLAGS_PURGABLE | VM_FLAGS_ANYWHERE);
if (ret == KERN_SUCCESS) {
return true;
}
heap_alloc:
moz_posix_memalign(&mBuf, aAlignment, aSize);
mHeap = true;
return !!mBuf;
}
VolatileBuffer::~VolatileBuffer()
{
if (OnHeap()) {
free(mBuf);
} else {
vm_deallocate(mach_task_self(), (vm_address_t)mBuf, mSize);
}
}
bool
VolatileBuffer::Lock(void** aBuf)
{
MOZ_ASSERT(mBuf, "Attempting to lock an uninitialized VolatileBuffer");
*aBuf = mBuf;
if (++mLockCount > 1 || OnHeap()) {
return true;
}
int state = VM_PURGABLE_NONVOLATILE;
kern_return_t ret =
vm_purgable_control(mach_task_self(),
(vm_address_t)mBuf,
VM_PURGABLE_SET_STATE,
&state);
return ret == KERN_SUCCESS && !(state & VM_PURGABLE_EMPTY);
}
void
VolatileBuffer::Unlock()
{
MOZ_ASSERT(mLockCount > 0, "VolatileBuffer unlocked too many times!");
if (--mLockCount || OnHeap()) {
return;
}
int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT;
DebugOnly<kern_return_t> ret =
vm_purgable_control(mach_task_self(),
(vm_address_t)mBuf,
VM_PURGABLE_SET_STATE,
&state);
MOZ_ASSERT(ret == KERN_SUCCESS, "Failed to set buffer as purgable");
}
bool
VolatileBuffer::OnHeap() const
{
return mHeap;
}
size_t
VolatileBuffer::HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
return OnHeap() ? aMallocSizeOf(mBuf) : 0;
}
size_t
VolatileBuffer::NonHeapSizeOfExcludingThis() const
{
if (OnHeap()) {
return 0;
}
unsigned long pagemask = getpagesize() - 1;
return (mSize + pagemask) & ~pagemask;
}
} // namespace mozilla

View File

@ -0,0 +1,173 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if defined(XP_WIN) || defined(XP_OS2)
# define MOZALLOC_EXPORT __declspec(dllexport)
#endif
#include "VolatileBuffer.h"
#include "mozilla/Assertions.h"
#include "mozilla/mozalloc.h"
#include <Windows.h>
#ifdef MOZ_MEMORY
extern "C" int posix_memalign(void** memptr, size_t alignment, size_t size);
#endif
#ifndef MEM_RESET_UNDO
#define MEM_RESET_UNDO 0x1000000
#endif
#define MIN_VOLATILE_ALLOC_SIZE 8192
namespace mozilla {
VolatileBuffer::VolatileBuffer()
: mBuf(nullptr)
, mSize(0)
, mLockCount(0)
, mHeap(false)
, mFirstLock(true)
{
}
bool
VolatileBuffer::Init(size_t aSize, size_t aAlignment)
{
MOZ_ASSERT(!mSize && !mBuf, "Init called twice");
MOZ_ASSERT(!(aAlignment % sizeof(void *)),
"Alignment must be multiple of pointer size");
mSize = aSize;
if (aSize < MIN_VOLATILE_ALLOC_SIZE) {
goto heap_alloc;
}
static bool sCheckedVersion = false;
static bool sUndoSupported = false;
if (!sCheckedVersion) {
OSVERSIONINFOEX verinfo = { 0 };
verinfo.dwOSVersionInfoSize = sizeof(verinfo);
verinfo.dwMajorVersion = 6;
verinfo.dwMinorVersion = 2;
DWORDLONG mask = 0;
VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
sUndoSupported = VerifyVersionInfo(&verinfo,
VER_MAJORVERSION | VER_MINORVERSION,
mask);
sCheckedVersion = true;
}
if (!sUndoSupported) {
goto heap_alloc;
}
mBuf = VirtualAllocEx(GetCurrentProcess(),
nullptr,
mSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (mBuf) {
return true;
}
heap_alloc:
#ifdef MOZ_MEMORY
posix_memalign(&mBuf, aAlignment, aSize);
#else
mBuf = _aligned_malloc(aSize, aAlignment);
#endif
mHeap = true;
return !!mBuf;
}
VolatileBuffer::~VolatileBuffer()
{
if (OnHeap()) {
#ifdef MOZ_MEMORY
free(mBuf);
#else
_aligned_free(mBuf);
#endif
} else {
VirtualFreeEx(GetCurrentProcess(), mBuf, 0, MEM_RELEASE);
}
}
bool
VolatileBuffer::Lock(void** aBuf)
{
MOZ_ASSERT(mBuf, "Attempting to lock an uninitialized VolatileBuffer");
*aBuf = mBuf;
if (++mLockCount > 1 || OnHeap()) {
return true;
}
// MEM_RESET_UNDO's behavior is undefined when called on memory that
// hasn't been MEM_RESET.
if (mFirstLock) {
mFirstLock = false;
return true;
}
void* addr = VirtualAllocEx(GetCurrentProcess(),
mBuf,
mSize,
MEM_RESET_UNDO,
PAGE_READWRITE);
return !!addr;
}
void
VolatileBuffer::Unlock()
{
MOZ_ASSERT(mLockCount > 0, "VolatileBuffer unlocked too many times!");
if (--mLockCount || OnHeap()) {
return;
}
void* addr = VirtualAllocEx(GetCurrentProcess(),
mBuf,
mSize,
MEM_RESET,
PAGE_READWRITE);
MOZ_ASSERT(addr, "Failed to MEM_RESET");
}
bool
VolatileBuffer::OnHeap() const
{
return mHeap;
}
size_t
VolatileBuffer::HeapSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
if (OnHeap()) {
#ifdef MOZ_MEMORY
return aMallocSizeOf(mBuf);
#else
return mSize;
#endif
}
return 0;
}
size_t
VolatileBuffer::NonHeapSizeOfExcludingThis() const
{
if (OnHeap()) {
return 0;
}
return (mSize + 4095) & ~4095;
}
} // namespace mozilla

View File

@ -10,6 +10,7 @@ EXPORTS.mozilla += [
'mozalloc.h',
'mozalloc_abort.h',
'mozalloc_oom.h',
'VolatileBuffer.h',
]
if CONFIG['MOZ_MSVC_STL_WRAP__RAISE'] or CONFIG['MOZ_MSVC_STL_WRAP__Throw']:
@ -38,6 +39,23 @@ UNIFIED_SOURCES += [
'mozalloc_oom.cpp',
]
if CONFIG['OS_TARGET'] == 'Android':
UNIFIED_SOURCES += [
'VolatileBufferAshmem.cpp',
]
elif CONFIG['OS_TARGET'] == 'Darwin':
UNIFIED_SOURCES += [
'VolatileBufferOSX.cpp',
]
elif CONFIG['OS_TARGET'] == 'WINNT':
UNIFIED_SOURCES += [
'VolatileBufferWindows.cpp',
]
else:
UNIFIED_SOURCES += [
'VolatileBufferFallback.cpp',
]
LIBRARY_NAME = 'mozalloc'
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
@ -47,3 +65,5 @@ else:
# The strndup declaration in string.h is in an ifdef __USE_GNU section
DEFINES['_GNU_SOURCE'] = True
TEST_TOOL_DIRS += ['tests']

View File

@ -0,0 +1,144 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TestHarness.h"
#include "mozilla/VolatileBuffer.h"
#include "mozilla/NullPtr.h"
#include <string.h>
#if defined(ANDROID)
#include <fcntl.h>
#include <linux/ashmem.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#elif defined(XP_DARWIN)
#include <mach/mach.h>
#endif
using namespace mozilla;
int main(int argc, char **argv)
{
ScopedXPCOM xpcom("VolatileBufferTests");
if (xpcom.failed()) {
return 1;
}
RefPtr<VolatileBuffer> heapbuf = new VolatileBuffer();
if (!heapbuf || !heapbuf->Init(512)) {
fail("Failed to initialize VolatileBuffer");
return 1;
}
{
VolatileBufferPtr<char> ptr(heapbuf);
if (ptr.WasBufferPurged()) {
fail("Buffer was immediately purged after initialization");
return 1;
}
if (!ptr) {
fail("Didn't get a pointer");
return 1;
}
}
RefPtr<VolatileBuffer> buf = new VolatileBuffer();
if (!buf || !buf->Init(16384)) {
fail("Failed to initialize VolatileBuffer");
return 1;
}
const char teststr[] = "foobar";
{
VolatileBufferPtr<char> ptr(buf);
if (ptr.WasBufferPurged()) {
fail("Buffer should not be purged immediately after initialization");
return 1;
}
if (!ptr) {
fail("Didn't get a pointer");
return 1;
}
{
VolatileBufferPtr<char> ptr2(buf);
if (ptr2.WasBufferPurged()) {
fail("Failed to Lock buffer again while currently locked");
return 1;
}
if (!ptr2) {
fail("Didn't get a pointer on the second lock");
return 1;
}
strcpy(ptr2, teststr);
}
}
{
VolatileBufferPtr<char> ptr(buf);
if (ptr.WasBufferPurged()) {
fail("Buffer was immediately purged after unlock");
return 1;
}
if (strcmp(ptr, teststr)) {
fail("Buffer failed to retain data after unlock");
return 1;
}
}
// Test purging if we know how to
#if defined(MOZ_WIDGET_GONK)
// This also works on Android, but we need root.
int fd = open("/" ASHMEM_NAME_DEF, O_RDWR);
if (fd < 0) {
fail("Failed to open ashmem device");
return 1;
}
if (ioctl(fd, ASHMEM_PURGE_ALL_CACHES, NULL) < 0) {
fail("Failed to purge ashmem caches");
return 1;
}
#elif defined(XP_DARWIN)
int state;
vm_purgable_control(mach_task_self(), (vm_address_t)NULL,
VM_PURGABLE_PURGE_ALL, &state);
#else
return 0;
#endif
if (!buf->NonHeapSizeOfExcludingThis()) {
fail("Buffer should not be allocated on heap");
return 1;
}
{
VolatileBufferPtr<char> ptr(buf);
if (!ptr.WasBufferPurged()) {
fail("Buffer should not be unpurged after forced purge");
return 1;
}
if (!strcmp(ptr, teststr)) {
fail("Purge did not actually purge data");
return 1;
}
}
{
VolatileBufferPtr<char> ptr(buf);
if (ptr.WasBufferPurged()) {
fail("Buffer still purged after lock");
return 1;
}
}
return 0;
}

View File

@ -0,0 +1,9 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
CPP_UNIT_TESTS += [
'TestVolatileBuffer.cpp',
]