//===-- Interface for freetrie --------------------------------------------===//
//
// 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_FREETRIE_H
#define LLVM_LIBC_SRC___SUPPORT_FREETRIE_H

#include "freelist.h"

namespace LIBC_NAMESPACE_DECL {

/// A trie of free lists.
///
/// This is an unusual little data structure originally from Doug Lea's malloc.
/// Finding the best fit from a set of differently-sized free list typically
/// required some kind of ordered map, and these are typically implemented using
/// a self-balancing binary search tree. Those are notorious for having a
/// relatively large number of special cases, while this trie has relatively
/// few, which helps with code size.
///
/// Operations on the trie are logarithmic not on the number of nodes within it,
/// but rather the fixed range of possible sizes that the trie can contain. This
/// means that the data structure would likely actually perform worse than an
/// e.g. red-black tree, but its implementation is still much simpler.
///
/// Each trie node's children subdivide the range of possible sizes into two
/// halves: a lower and an upper. The node itself holds a free list of some size
/// within its range. This makes it possible to summarily replace any node with
/// any leaf within its subtrie, which makes it very straightforward to remove a
/// node. Insertion is also simple; the only real complexity lies with finding
/// the best fit. This can still be done in logarithmic time with only a few
/// cases to consider.
///
/// The trie refers to, but does not own, the Nodes that comprise it.
class FreeTrie {
public:
  /// A trie node that is also a free list. Only the head node of each list is
  /// actually part of the trie. The subtrie contains a continous SizeRange of
  /// free lists. The lower and upper subtrie's contain the lower and upper half
  /// of the subtries range. There is no direct relationship between the size of
  /// this node's free list and the contents of the lower and upper subtries.
  class Node : public FreeList::Node {
    /// The child subtrie covering the lower half of this subtrie's size range.
    /// Undefined if this is not the head of the list.
    Node *lower;
    /// The child subtrie covering the upper half of this subtrie's size range.
    /// Undefined if this is not the head of the list.
    Node *upper;
    /// The parent subtrie. nullptr if this is the root or not the head of the
    /// list.
    Node *parent;

    friend class FreeTrie;
  };

  /// Power-of-two range of sizes covered by a subtrie.
  struct SizeRange {
    size_t min;
    size_t width;

    LIBC_INLINE constexpr SizeRange(size_t min, size_t width)
        : min(min), width(width) {
      LIBC_ASSERT(!(width & (width - 1)) && "width must be a power of two");
    }

    /// @returns The lower half of the size range.
    LIBC_INLINE SizeRange lower() const { return {min, width / 2}; }

    /// @returns The upper half of the size range.
    LIBC_INLINE SizeRange upper() const { return {min + width / 2, width / 2}; }

    /// @returns The largest size in this range.
    LIBC_INLINE size_t max() const { return min + (width - 1); }

    /// @returns Whether the range contains the given size.
    LIBC_INLINE bool contains(size_t size) const {
      return min <= size && size < min + width;
    }
  };

  LIBC_INLINE constexpr FreeTrie() : FreeTrie(SizeRange{0, 0}) {}
  LIBC_INLINE constexpr FreeTrie(SizeRange range) : range(range) {}

  /// Sets the range of possible block sizes. This can only be called when the
  /// trie is empty.
  LIBC_INLINE void set_range(FreeTrie::SizeRange range) {
    LIBC_ASSERT(empty() && "cannot change the range of a preexisting trie");
    this->range = range;
  }

  /// @returns Whether the trie contains any blocks.
  LIBC_INLINE bool empty() const { return !root; }

  /// Push a block to the trie.
  void push(Block *block);

  /// Remove a node from this trie node's free list.
  void remove(Node *node);

  /// @returns A smallest node that can allocate the given size; otherwise
  /// nullptr.
  Node *find_best_fit(size_t size);

private:
  /// @returns Whether a node is the head of its containing freelist.
  bool is_head(Node *node) const { return node->parent || node == root; }

  /// Replaces references to one node with another (or nullptr) in all adjacent
  /// parent and child nodes.
  void replace_node(Node *node, Node *new_node);

  Node *root = nullptr;
  SizeRange range;
};

LIBC_INLINE void FreeTrie::push(Block *block) {
  LIBC_ASSERT(block->inner_size_free() >= sizeof(Node) &&
              "block too small to accomodate free trie node");
  size_t size = block->inner_size();
  LIBC_ASSERT(range.contains(size) && "requested size out of trie range");

  // Find the position in the tree to push to.
  Node **cur = &root;
  Node *parent = nullptr;
  SizeRange cur_range = range;
  while (*cur && (*cur)->size() != size) {
    LIBC_ASSERT(cur_range.contains(size) && "requested size out of trie range");
    parent = *cur;
    if (size <= cur_range.lower().max()) {
      cur = &(*cur)->lower;
      cur_range = cur_range.lower();
    } else {
      cur = &(*cur)->upper;
      cur_range = cur_range.upper();
    }
  }

  Node *node = new (block->usable_space()) Node;
  FreeList list = *cur;
  if (list.empty()) {
    node->parent = parent;
    node->lower = node->upper = nullptr;
  } else {
    node->parent = nullptr;
  }
  list.push(node);
  *cur = static_cast<Node *>(list.begin());
}

LIBC_INLINE FreeTrie::Node *FreeTrie::find_best_fit(size_t size) {
  if (empty() || range.max() < size)
    return nullptr;

  Node *cur = root;
  SizeRange cur_range = range;
  Node *best_fit = nullptr;
  Node *deferred_upper_trie = nullptr;
  FreeTrie::SizeRange deferred_upper_range{0, 0};

  while (true) {
    LIBC_ASSERT(cur_range.contains(cur->size()) &&
                "trie node size out of range");
    LIBC_ASSERT(cur_range.max() >= size &&
                "range could not fit requested size");
    LIBC_ASSERT((!best_fit || cur_range.min < best_fit->size()) &&
                "range could not contain a best fit");

    // If the current node is an exact fit, it is a best fit.
    if (cur->size() == size)
      return cur;

    if (cur->size() > size && (!best_fit || cur->size() < best_fit->size())) {
      // The current node is a better fit.
      best_fit = cur;

      // If there is a deferred upper subtrie, then the current node is
      // somewhere in its lower sibling subtrie. That means that the new best
      // fit is better than the best fit in the deferred subtrie.
      LIBC_ASSERT(
          (!deferred_upper_trie ||
           deferred_upper_range.min > best_fit->size()) &&
          "deferred upper subtrie should be outclassed by new best fit");
      deferred_upper_trie = nullptr;
    }

    // Determine which subtries might contain the best fit.
    bool lower_impossible = !cur->lower || cur_range.lower().max() < size;
    bool upper_impossible =
        !cur->upper ||
        // If every node in the lower trie fits
        (!lower_impossible && cur_range.min >= size) ||
        // If every node in the upper trie is worse than the current best
        (best_fit && cur_range.upper().min >= best_fit->size());

    if (lower_impossible && upper_impossible) {
      if (!deferred_upper_trie)
        return best_fit;
      // Scan the deferred upper subtrie and consider whether any element within
      // provides a better fit.
      //
      // This can only ever be reached once. In a deferred upper subtrie, every
      // node fits, so the higher of two subtries can never contain a best fit.
      cur = deferred_upper_trie;
      cur_range = deferred_upper_range;
      deferred_upper_trie = nullptr;
      continue;
    }

    if (lower_impossible) {
      cur = cur->upper;
      cur_range = cur_range.upper();
    } else if (upper_impossible) {
      cur = cur->lower;
      cur_range = cur_range.lower();
    } else {
      // Both subtries might contain a better fit. Any fit in the lower subtrie
      // is better than the any fit in the upper subtrie, so scan the lower
      // and return to the upper only if no better fits were found. (Any better
      // fit found clears the deferred upper subtrie.)
      LIBC_ASSERT((!deferred_upper_trie ||
                   cur_range.upper().max() < deferred_upper_range.min) &&
                  "old deferred upper subtrie should be outclassed by new");
      deferred_upper_trie = cur->upper;
      deferred_upper_range = cur_range.upper();
      cur = cur->lower;
      cur_range = cur_range.lower();
    }
  }
}

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_FREETRIE_H
