/*
 * Copyright (C) 2008 Apple 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:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
 */

#ifndef jit_ExecutableAllocator_h
#define jit_ExecutableAllocator_h

#include "mozilla/EnumeratedArray.h"

#include <stddef.h>  // for ptrdiff_t

#include "jit/ProcessExecutableMemory.h"
#include "js/AllocPolicy.h"
#include "js/HashTable.h"
#include "js/TypeDecls.h"
#include "js/Vector.h"

namespace JS {
struct CodeSizes;
}  // namespace JS

namespace js {
namespace jit {

enum class CodeKind : uint8_t { Ion, Baseline, RegExp, Other, Count };

class ExecutableAllocator;

// These are reference-counted. A new one starts with a count of 1.
class ExecutablePool {
  friend class ExecutableAllocator;

 private:
  struct Allocation {
    char* pages;
    size_t size;
  };

  ExecutableAllocator* m_allocator;
  char* m_freePtr;
  char* m_end;
  Allocation m_allocation;

  // Reference count for automatic reclamation.
  unsigned m_refCount : 31;

  // Flag that can be used by algorithms operating on pools.
  bool m_mark : 1;

  // Number of bytes currently allocated for each CodeKind.
  mozilla::EnumeratedArray<CodeKind, size_t, size_t(CodeKind::Count)>
      m_codeBytes;

 public:
  void release(bool willDestroy = false);
  void release(size_t n, CodeKind kind);

  void addRef();

  ExecutablePool(ExecutableAllocator* allocator, Allocation a)
      : m_allocator(allocator),
        m_freePtr(a.pages),
        m_end(m_freePtr + a.size),
        m_allocation(a),
        m_refCount(1),
        m_mark(false) {
    for (size_t& count : m_codeBytes) {
      count = 0;
    }
  }

  ~ExecutablePool();

  ExecutablePool(const ExecutablePool&) = delete;
  void operator=(const ExecutablePool&) = delete;

  void mark() {
    MOZ_ASSERT(!m_mark);
    m_mark = true;
  }
  void unmark() {
    MOZ_ASSERT(m_mark);
    m_mark = false;
  }
  bool isMarked() const { return m_mark; }

 private:
  void* alloc(size_t n, CodeKind kind);

  size_t available() const;

  // Returns the number of bytes that are currently in use (referenced by
  // live JitCode objects).
  size_t usedCodeBytes() const {
    size_t res = 0;
    for (size_t count : m_codeBytes) {
      res += count;
    }
    return res;
  }
};

struct JitPoisonRange {
  jit::ExecutablePool* pool;
  void* start;
  size_t size;

  JitPoisonRange(jit::ExecutablePool* pool, void* start, size_t size)
      : pool(pool), start(start), size(size) {}
};

using JitPoisonRangeVector = Vector<JitPoisonRange, 0, SystemAllocPolicy>;

class ExecutableAllocator {
 public:
  ExecutableAllocator() = default;
  ~ExecutableAllocator();

  ExecutableAllocator(const ExecutableAllocator&) = delete;
  void operator=(const ExecutableAllocator&) = delete;

  void purge();

  // alloc() returns a pointer to some memory, and also (by reference) a
  // pointer to reference-counted pool. The caller owns a reference to the
  // pool; i.e. alloc() increments the count before returning the object.
  void* alloc(JSContext* cx, size_t n, ExecutablePool** poolp, CodeKind type);

  void releasePoolPages(ExecutablePool* pool);

  void addSizeOfCode(JS::CodeSizes* sizes) const;

 private:
  static const size_t OVERSIZE_ALLOCATION = size_t(-1);

  static size_t roundUpAllocationSize(size_t request, size_t granularity);

  // On OOM, this will return an Allocation where pages is nullptr.
  ExecutablePool::Allocation systemAlloc(size_t n);
  static void systemRelease(const ExecutablePool::Allocation& alloc);

  ExecutablePool* createPool(size_t n);
  ExecutablePool* poolForSize(size_t n);

  static void reprotectPool(JSRuntime* rt, ExecutablePool* pool,
                            ProtectionSetting protection,
                            MustFlushICache flushICache);

 public:
  [[nodiscard]] static bool makeWritable(void* start, size_t size) {
    return ReprotectRegion(start, size, ProtectionSetting::Writable,
                           MustFlushICache::No);
  }

  [[nodiscard]] static bool makeExecutableAndFlushICache(void* start,
                                                         size_t size) {
    return ReprotectRegion(start, size, ProtectionSetting::Executable,
                           MustFlushICache::Yes);
  }

  static void poisonCode(JSRuntime* rt, JitPoisonRangeVector& ranges);

 private:
  // These are strong references;  they keep pools alive.
  static const size_t maxSmallPools = 4;
  using SmallExecPoolVector =
      js::Vector<ExecutablePool*, maxSmallPools, js::SystemAllocPolicy>;
  SmallExecPoolVector m_smallPools;

  // All live pools are recorded here, just for stats purposes.  These are
  // weak references;  they don't keep pools alive.  When a pool is destroyed
  // its reference is removed from m_pools.
  using ExecPoolHashSet =
      js::HashSet<ExecutablePool*, js::DefaultHasher<ExecutablePool*>,
                  js::SystemAllocPolicy>;
  ExecPoolHashSet m_pools;  // All pools, just for stats purposes.
};

}  // namespace jit
}  // namespace js

#endif /* jit_ExecutableAllocator_h */
