/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * 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 jit_JitAllocPolicy_h
#define jit_JitAllocPolicy_h

#include "mozilla/Attributes.h"
#include "mozilla/GuardObjects.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/TypeTraits.h"

#include "jscntxt.h"

#include "ds/LifoAlloc.h"
#include "jit/InlineList.h"
#include "jit/Ion.h"

namespace js {
namespace jit {

class TempAllocator
{
    LifoAllocScope lifoScope_;

  public:
    // Most infallible JIT allocations are small, so we use a ballast of 16
    // KiB. And with a ballast of 16 KiB, a chunk size of 32 KiB works well,
    // because TempAllocators with a peak allocation size of less than 16 KiB
    // (which is most of them) only have to allocate a single chunk.
    static const size_t BallastSize;            // 16 KiB
    static const size_t PreferredLifoChunkSize; // 32 KiB

    explicit TempAllocator(LifoAlloc* lifoAlloc)
      : lifoScope_(lifoAlloc)
    {
        lifoAlloc->setAsInfallibleByDefault();
    }

    void* allocateInfallible(size_t bytes)
    {
        return lifoScope_.alloc().allocInfallible(bytes);
    }

    MOZ_MUST_USE void* allocate(size_t bytes)
    {
        LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc());
        void* p = lifoScope_.alloc().alloc(bytes);
        if (!ensureBallast())
            return nullptr;
        return p;
    }

    template <typename T>
    MOZ_MUST_USE T* allocateArray(size_t n)
    {
        LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc());
        size_t bytes;
        if (MOZ_UNLIKELY(!CalculateAllocSize<T>(n, &bytes)))
            return nullptr;
        T* p = static_cast<T*>(lifoScope_.alloc().alloc(bytes));
        if (MOZ_UNLIKELY(!ensureBallast()))
            return nullptr;
        return p;
    }

    // View this allocator as a fallible allocator.
    struct Fallible { TempAllocator& alloc; };
    Fallible fallible() { return { *this }; }

    LifoAlloc* lifoAlloc() {
        return &lifoScope_.alloc();
    }

    MOZ_MUST_USE bool ensureBallast() {
        JS_OOM_POSSIBLY_FAIL_BOOL();
        return lifoScope_.alloc().ensureUnusedApproximate(BallastSize);
    }
};

class JitAllocPolicy
{
    TempAllocator& alloc_;

  public:
    MOZ_IMPLICIT JitAllocPolicy(TempAllocator& alloc)
      : alloc_(alloc)
    {}
    template <typename T>
    T* maybe_pod_malloc(size_t numElems) {
        size_t bytes;
        if (MOZ_UNLIKELY(!CalculateAllocSize<T>(numElems, &bytes)))
            return nullptr;
        return static_cast<T*>(alloc_.allocate(bytes));
    }
    template <typename T>
    T* maybe_pod_calloc(size_t numElems) {
        T* p = maybe_pod_malloc<T>(numElems);
        if (MOZ_LIKELY(p))
            memset(p, 0, numElems * sizeof(T));
        return p;
    }
    template <typename T>
    T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
        T* n = pod_malloc<T>(newSize);
        if (MOZ_UNLIKELY(!n))
            return n;
        MOZ_ASSERT(!(oldSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value));
        memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T)));
        return n;
    }
    template <typename T>
    T* pod_malloc(size_t numElems) {
        return maybe_pod_malloc<T>(numElems);
    }
    template <typename T>
    T* pod_calloc(size_t numElems) {
        return maybe_pod_calloc<T>(numElems);
    }
    template <typename T>
    T* pod_realloc(T* ptr, size_t oldSize, size_t newSize) {
        return maybe_pod_realloc<T>(ptr, oldSize, newSize);
    }
    void free_(void* p) {
    }
    void reportAllocOverflow() const {
    }
    MOZ_MUST_USE bool checkSimulatedOOM() const {
        return !js::oom::ShouldFailWithOOM();
    }
};

class AutoJitContextAlloc
{
    TempAllocator tempAlloc_;
    JitContext* jcx_;
    TempAllocator* prevAlloc_;

  public:
    explicit AutoJitContextAlloc(JSContext* cx)
      : tempAlloc_(&cx->tempLifoAlloc()),
        jcx_(GetJitContext()),
        prevAlloc_(jcx_->temp)
    {
        jcx_->temp = &tempAlloc_;
    }

    ~AutoJitContextAlloc() {
        MOZ_ASSERT(jcx_->temp == &tempAlloc_);
        jcx_->temp = prevAlloc_;
    }
};

struct TempObject
{
    inline void* operator new(size_t nbytes, TempAllocator::Fallible view) throw() {
        return view.alloc.allocate(nbytes);
    }
    inline void* operator new(size_t nbytes, TempAllocator& alloc) {
        return alloc.allocateInfallible(nbytes);
    }
    template <class T>
    inline void* operator new(size_t nbytes, T* pos) {
        static_assert(mozilla::IsConvertible<T*, TempObject*>::value,
                      "Placement new argument type must inherit from TempObject");
        return pos;
    }
    template <class T>
    inline void* operator new(size_t nbytes, mozilla::NotNullTag, T* pos) {
        static_assert(mozilla::IsConvertible<T*, TempObject*>::value,
                      "Placement new argument type must inherit from TempObject");
        MOZ_ASSERT(pos);
        return pos;
    }        
};

template <typename T>
class TempObjectPool
{
    TempAllocator* alloc_;
    InlineForwardList<T> freed_;

  public:
    TempObjectPool()
      : alloc_(nullptr)
    {}
    void setAllocator(TempAllocator& alloc) {
        MOZ_ASSERT(freed_.empty());
        alloc_ = &alloc;
    }
    T* allocate() {
        MOZ_ASSERT(alloc_);
        if (freed_.empty())
            return new(alloc_->fallible()) T();
        return freed_.popFront();
    }
    void free(T* obj) {
        freed_.pushFront(obj);
    }
    void clear() {
        freed_.clear();
    }
};

} // namespace jit
} // namespace js

#endif /* jit_JitAllocPolicy_h */