/* 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/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/RefCounted.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 and VolatileBufferPtr are threadsafe.
 */

namespace mozilla {

class VolatileBuffer
{
  friend class VolatileBufferPtr_base;
public:
  MOZ_DECLARE_REFCOUNTED_TYPENAME(VolatileBuffer)
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(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;
  bool OnHeap() const;

protected:
  bool Lock(void** aBuf);
  void Unlock();

private:
  ~VolatileBuffer();

  /**
   * Protects mLockCount, mFirstLock, and changes to the volatility of our
   * buffer.  Other member variables are read-only except in Init() and the
   * destructor.
   */
  Mutex mMutex;

  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:
  explicit VolatileBufferPtr_base(VolatileBuffer* vbuf)
    : mVBuf(vbuf)
    , mMapping(nullptr)
    , mPurged(false)
  {
    Lock();
  }

  ~VolatileBufferPtr_base() {
    Unlock();
  }

  bool WasBufferPurged() const {
    return mPurged;
  }

protected:
  RefPtr<VolatileBuffer> mVBuf;
  void* mMapping;

  void Set(VolatileBuffer* vbuf) {
    Unlock();
    mVBuf = vbuf;
    Lock();
  }

private:
  bool mPurged;

  void Lock() {
    if (mVBuf) {
      mPurged = !mVBuf->Lock(&mMapping);
    } else {
      mMapping = nullptr;
      mPurged = false;
    }
  }

  void Unlock() {
    if (mVBuf) {
      mVBuf->Unlock();
    }
  }
};

template <class T>
class VolatileBufferPtr : public VolatileBufferPtr_base
{
public:
  explicit VolatileBufferPtr(VolatileBuffer* vbuf) : VolatileBufferPtr_base(vbuf) {}
  VolatileBufferPtr() : VolatileBufferPtr_base(nullptr) {}

  VolatileBufferPtr(VolatileBufferPtr&& aOther)
    : VolatileBufferPtr_base(aOther.mVBuf)
  {
    aOther.Set(nullptr);
  }

  operator T*() const {
    return (T*) mMapping;
  }

  VolatileBufferPtr& operator=(VolatileBuffer* aVBuf)
  {
    Set(aVBuf);
    return *this;
  }

  VolatileBufferPtr& operator=(VolatileBufferPtr&& aOther)
  {
    MOZ_ASSERT(this != &aOther, "Self-moves are prohibited");
    Set(aOther.mVBuf);
    aOther.Set(nullptr);
    return *this;
  }

private:
  VolatileBufferPtr(VolatileBufferPtr const& vbufptr) = delete;
};

} // namespace mozilla

#endif /* mozalloc_VolatileBuffer_h */