/*
 * Copyright 2019 The libgav1 Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef LIBGAV1_SRC_UTILS_MEMORY_H_
#define LIBGAV1_SRC_UTILS_MEMORY_H_

#if defined(__ANDROID__) || defined(_MSC_VER)
#include <malloc.h>
#endif

#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <new>

namespace libgav1 {

enum {
// The byte alignment required for buffers used with SIMD code to be read or
// written with aligned operations.
#if defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || \
    defined(_M_X64)
  kMaxAlignment = 32,  // extended alignment is safe on x86.
#else
  kMaxAlignment = alignof(max_align_t),
#endif
};

// AlignedAlloc, AlignedFree
//
// void* AlignedAlloc(size_t alignment, size_t size);
//   Allocate aligned memory.
//   |alignment| must be a power of 2.
//   Unlike posix_memalign(), |alignment| may be smaller than sizeof(void*).
//   Unlike aligned_alloc(), |size| does not need to be a multiple of
//   |alignment|.
//   The returned pointer should be freed by AlignedFree().
//
// void AlignedFree(void* aligned_memory);
//   Free aligned memory.

#if defined(_MSC_VER)  // MSVC

inline void* AlignedAlloc(size_t alignment, size_t size) {
  return _aligned_malloc(size, alignment);
}

inline void AlignedFree(void* aligned_memory) { _aligned_free(aligned_memory); }

#else  // !defined(_MSC_VER)

inline void* AlignedAlloc(size_t alignment, size_t size) {
#if defined(__ANDROID__)
  // Although posix_memalign() was introduced in Android API level 17, it is
  // more convenient to use memalign(). Unlike glibc, Android does not consider
  // memalign() an obsolete function.
  return memalign(alignment, size);
#else  // !defined(__ANDROID__)
  void* ptr = nullptr;
  // posix_memalign requires that the requested alignment be at least
  // sizeof(void*). In this case, fall back on malloc which should return
  // memory aligned to at least the size of a pointer.
  const size_t required_alignment = sizeof(void*);
  if (alignment < required_alignment) return malloc(size);
  const int error = posix_memalign(&ptr, alignment, size);
  if (error != 0) {
    errno = error;
    return nullptr;
  }
  return ptr;
#endif  // defined(__ANDROID__)
}

inline void AlignedFree(void* aligned_memory) { free(aligned_memory); }

#endif  // defined(_MSC_VER)

inline void Memset(uint8_t* const dst, int value, size_t count) {
  memset(dst, value, count);
}

inline void Memset(uint16_t* const dst, int value, size_t count) {
  for (size_t i = 0; i < count; ++i) {
    dst[i] = static_cast<uint16_t>(value);
  }
}

struct MallocDeleter {
  void operator()(void* ptr) const { free(ptr); }
};

struct AlignedDeleter {
  void operator()(void* ptr) const { AlignedFree(ptr); }
};

template <typename T>
using AlignedUniquePtr = std::unique_ptr<T, AlignedDeleter>;

// Allocates aligned memory for an array of |count| elements of type T.
template <typename T>
inline AlignedUniquePtr<T> MakeAlignedUniquePtr(size_t alignment,
                                                size_t count) {
  return AlignedUniquePtr<T>(
      static_cast<T*>(AlignedAlloc(alignment, count * sizeof(T))));
}

// A base class with custom new and delete operators. The exception-throwing
// new operators are deleted. The "new (std::nothrow)" form must be used.
//
// The new operators return nullptr if the requested size is greater than
// 0x40000000 bytes (1 GB). TODO(wtc): Make the maximum allocable memory size
// a compile-time configuration macro.
//
// See https://en.cppreference.com/w/cpp/memory/new/operator_new and
// https://en.cppreference.com/w/cpp/memory/new/operator_delete.
//
// NOTE: The allocation and deallocation functions are static member functions
// whether the keyword 'static' is used or not.
struct Allocable {
  // Class-specific allocation functions.
  static void* operator new(size_t size) = delete;
  static void* operator new[](size_t size) = delete;

  // Class-specific non-throwing allocation functions
  static void* operator new(size_t size, const std::nothrow_t& tag) noexcept {
    if (size > 0x40000000) return nullptr;
    return ::operator new(size, tag);
  }
  static void* operator new[](size_t size, const std::nothrow_t& tag) noexcept {
    if (size > 0x40000000) return nullptr;
    return ::operator new[](size, tag);
  }

  // Class-specific deallocation functions.
  static void operator delete(void* ptr) noexcept { ::operator delete(ptr); }
  static void operator delete[](void* ptr) noexcept {
    ::operator delete[](ptr);
  }

  // Only called if new (std::nothrow) is used and the constructor throws an
  // exception.
  static void operator delete(void* ptr, const std::nothrow_t& tag) noexcept {
    ::operator delete(ptr, tag);
  }
  // Only called if new[] (std::nothrow) is used and the constructor throws an
  // exception.
  static void operator delete[](void* ptr, const std::nothrow_t& tag) noexcept {
    ::operator delete[](ptr, tag);
  }
};

// A variant of Allocable that forces allocations to be aligned to
// kMaxAlignment bytes. This is intended for use with classes that use
// alignas() with this value. C++17 aligned new/delete are used if available,
// otherwise we use AlignedAlloc/Free.
struct MaxAlignedAllocable {
  // Class-specific allocation functions.
  static void* operator new(size_t size) = delete;
  static void* operator new[](size_t size) = delete;

  // Class-specific non-throwing allocation functions
  static void* operator new(size_t size, const std::nothrow_t& tag) noexcept {
    if (size > 0x40000000) return nullptr;
#ifdef __cpp_aligned_new
    return ::operator new(size, std::align_val_t(kMaxAlignment), tag);
#else
    static_cast<void>(tag);
    return AlignedAlloc(kMaxAlignment, size);
#endif
  }
  static void* operator new[](size_t size, const std::nothrow_t& tag) noexcept {
    if (size > 0x40000000) return nullptr;
#ifdef __cpp_aligned_new
    return ::operator new[](size, std::align_val_t(kMaxAlignment), tag);
#else
    static_cast<void>(tag);
    return AlignedAlloc(kMaxAlignment, size);
#endif
  }

  // Class-specific deallocation functions.
  static void operator delete(void* ptr) noexcept {
#ifdef __cpp_aligned_new
    ::operator delete(ptr, std::align_val_t(kMaxAlignment));
#else
    AlignedFree(ptr);
#endif
  }
  static void operator delete[](void* ptr) noexcept {
#ifdef __cpp_aligned_new
    ::operator delete[](ptr, std::align_val_t(kMaxAlignment));
#else
    AlignedFree(ptr);
#endif
  }

  // Only called if new (std::nothrow) is used and the constructor throws an
  // exception.
  static void operator delete(void* ptr, const std::nothrow_t& tag) noexcept {
#ifdef __cpp_aligned_new
    ::operator delete(ptr, std::align_val_t(kMaxAlignment), tag);
#else
    static_cast<void>(tag);
    AlignedFree(ptr);
#endif
  }
  // Only called if new[] (std::nothrow) is used and the constructor throws an
  // exception.
  static void operator delete[](void* ptr, const std::nothrow_t& tag) noexcept {
#ifdef __cpp_aligned_new
    ::operator delete[](ptr, std::align_val_t(kMaxAlignment), tag);
#else
    static_cast<void>(tag);
    AlignedFree(ptr);
#endif
  }
};

}  // namespace libgav1

#endif  // LIBGAV1_SRC_UTILS_MEMORY_H_
