//===-- Interface for freelist_malloc -------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC___SUPPORT_FREELIST_H
#define LLVM_LIBC_SRC___SUPPORT_FREELIST_H

#include "src/__support/CPP/array.h"
#include "src/__support/CPP/cstddef.h"
#include "src/__support/CPP/new.h"
#include "src/__support/CPP/span.h"
#include "src/__support/fixedvector.h"
#include "src/__support/macros/config.h"

namespace LIBC_NAMESPACE_DECL {

using cpp::span;

/// Basic [freelist](https://en.wikipedia.org/wiki/Free_list) implementation
/// for an allocator. This implementation buckets by chunk size, with a list
/// of user-provided buckets. Each bucket is a linked list of storage chunks.
/// Because this freelist uses the added chunks themselves as list nodes, there
/// is a lower bound of `sizeof(FreeList.FreeListNode)` bytes for chunks which
/// can be added to this freelist. There is also an implicit bucket for
/// "everything else", for chunks which do not fit into a bucket.
///
/// Each added chunk will be added to the smallest bucket under which it fits.
/// If it does not fit into any user-provided bucket, it will be added to the
/// default bucket.
///
/// As an example, assume that the `FreeList` is configured with buckets of
/// sizes {64, 128, 256, and 512} bytes. The internal state may look like the
/// following:
///
/// @code{.unparsed}
/// bucket[0] (64B) --> chunk[12B] --> chunk[42B] --> chunk[64B] --> NULL
/// bucket[1] (128B) --> chunk[65B] --> chunk[72B] --> NULL
/// bucket[2] (256B) --> NULL
/// bucket[3] (512B) --> chunk[312B] --> chunk[512B] --> chunk[416B] --> NULL
/// bucket[4] (implicit) --> chunk[1024B] --> chunk[513B] --> NULL
/// @endcode
///
/// Note that added chunks should be aligned to a 4-byte boundary.
template <size_t NUM_BUCKETS = 6> class FreeList {
public:
  // Remove copy/move ctors
  FreeList(const FreeList &other) = delete;
  FreeList(FreeList &&other) = delete;
  FreeList &operator=(const FreeList &other) = delete;
  FreeList &operator=(FreeList &&other) = delete;

  /// Adds a chunk to this freelist.
  bool add_chunk(cpp::span<cpp::byte> chunk);

  /// Finds an eligible chunk for an allocation of size `size`.
  ///
  /// @note This returns the first allocation possible within a given bucket;
  /// It does not currently optimize for finding the smallest chunk.
  ///
  /// @returns
  /// * On success - A span representing the chunk.
  /// * On failure (e.g. there were no chunks available for that allocation) -
  ///   A span with a size of 0.
  cpp::span<cpp::byte> find_chunk(size_t size) const;

  template <typename Cond> cpp::span<cpp::byte> find_chunk_if(Cond op) const;

  /// Removes a chunk from this freelist.
  bool remove_chunk(cpp::span<cpp::byte> chunk);

  /// For a given size, find which index into chunks_ the node should be written
  /// to.
  constexpr size_t find_chunk_ptr_for_size(size_t size, bool non_null) const;

  struct FreeListNode {
    FreeListNode *next;
    size_t size;
  };

  constexpr void set_freelist_node(FreeListNode &node,
                                   cpp::span<cpp::byte> chunk);

  constexpr explicit FreeList(const cpp::array<size_t, NUM_BUCKETS> &sizes)
      : chunks_(NUM_BUCKETS + 1, 0), sizes_(sizes.begin(), sizes.end()) {}

private:
  FixedVector<FreeList::FreeListNode *, NUM_BUCKETS + 1> chunks_;
  FixedVector<size_t, NUM_BUCKETS> sizes_;
};

template <size_t NUM_BUCKETS>
constexpr void FreeList<NUM_BUCKETS>::set_freelist_node(FreeListNode &node,
                                                        span<cpp::byte> chunk) {
  // Add it to the correct list.
  size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), false);
  node.size = chunk.size();
  node.next = chunks_[chunk_ptr];
  chunks_[chunk_ptr] = &node;
}

template <size_t NUM_BUCKETS>
bool FreeList<NUM_BUCKETS>::add_chunk(span<cpp::byte> chunk) {
  // Check that the size is enough to actually store what we need
  if (chunk.size() < sizeof(FreeListNode))
    return false;

  FreeListNode *node = ::new (chunk.data()) FreeListNode;
  set_freelist_node(*node, chunk);

  return true;
}

template <size_t NUM_BUCKETS>
template <typename Cond>
span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk_if(Cond op) const {
  for (FreeListNode *node : chunks_) {
    while (node != nullptr) {
      span<cpp::byte> chunk(reinterpret_cast<cpp::byte *>(node), node->size);
      if (op(chunk))
        return chunk;

      node = node->next;
    }
  }

  return {};
}

template <size_t NUM_BUCKETS>
span<cpp::byte> FreeList<NUM_BUCKETS>::find_chunk(size_t size) const {
  if (size == 0)
    return span<cpp::byte>();

  size_t chunk_ptr = find_chunk_ptr_for_size(size, true);

  // Check that there's data. This catches the case where we run off the
  // end of the array
  if (chunks_[chunk_ptr] == nullptr)
    return span<cpp::byte>();

  // Now iterate up the buckets, walking each list to find a good candidate
  for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
    FreeListNode *node = chunks_[static_cast<unsigned short>(i)];

    while (node != nullptr) {
      if (node->size >= size)
        return span<cpp::byte>(reinterpret_cast<cpp::byte *>(node), node->size);

      node = node->next;
    }
  }

  // If we get here, we've checked every block in every bucket. There's
  // nothing that can support this allocation.
  return span<cpp::byte>();
}

template <size_t NUM_BUCKETS>
bool FreeList<NUM_BUCKETS>::remove_chunk(span<cpp::byte> chunk) {
  size_t chunk_ptr = find_chunk_ptr_for_size(chunk.size(), true);

  // Check head first.
  if (chunks_[chunk_ptr] == nullptr)
    return false;

  FreeListNode *node = chunks_[chunk_ptr];
  if (reinterpret_cast<cpp::byte *>(node) == chunk.data()) {
    chunks_[chunk_ptr] = node->next;
    return true;
  }

  // No? Walk the nodes.
  node = chunks_[chunk_ptr];

  while (node->next != nullptr) {
    if (reinterpret_cast<cpp::byte *>(node->next) == chunk.data()) {
      // Found it, remove this node out of the chain
      node->next = node->next->next;
      return true;
    }

    node = node->next;
  }

  return false;
}

template <size_t NUM_BUCKETS>
constexpr size_t
FreeList<NUM_BUCKETS>::find_chunk_ptr_for_size(size_t size,
                                               bool non_null) const {
  size_t chunk_ptr = 0;
  for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
    if (sizes_[chunk_ptr] >= size &&
        (!non_null || chunks_[chunk_ptr] != nullptr)) {
      break;
    }
  }

  return chunk_ptr;
}

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_FREELIST_H
