[libc-commits] [libc] [libc] Implement TLSF FreeStore with Trie Overflow Bin (PR #203415)

Daniel Thornburgh via libc-commits libc-commits at lists.llvm.org
Fri Jul 31 15:49:37 PDT 2026


================
@@ -7,113 +7,387 @@
 //===----------------------------------------------------------------------===//
 ///
 /// \file
-/// Interface for freestore.
+/// This file contains a two-level segregated fit free block store.
 ///
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_SRC___SUPPORT_FREESTORE_H
 #define LLVM_LIBC_SRC___SUPPORT_FREESTORE_H
 
-#include "freetrie.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/block.h"
+#include "src/__support/freelist.h"
+#include "src/__support/freetrie.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/optimization.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-/// A best-fit store of variously-sized free blocks. Blocks can be inserted and
-/// removed in logarithmic time.
-class FreeStore {
-  friend class FreeListHeap;
-
-public:
-  FreeStore() = default;
-  FreeStore(const FreeStore &other) = delete;
-  FreeStore &operator=(const FreeStore &other) = delete;
-
-  /// 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) {
-    large_trie.set_range(range);
-  }
-
-  /// Insert a free block. If the block is too small to be tracked, nothing
-  /// happens.
-  void insert(BlockRef block);
+/// Default configuration for TLSFFreeStore.
+struct DefaultFreeStoreConfig {
+  static constexpr size_t UNIT_SIZE = BlockRef::MIN_ALIGN;
+  static constexpr size_t STEP_SIZE_BITS = 3;
+  static constexpr size_t NUM_STEP_BITS = 2;
+  static constexpr size_t NUM_TABLE_ENTRIES = sizeof(uintptr_t) == 8 ? 3 : 6;
+  static constexpr bool USE_TRIE_FOR_OVERFLOW_BIN = true;
+  static constexpr size_t LINEAR_SCAN_LIMIT = 16;
+};
 
-  /// Remove a free block. If the block is too small to be tracked, nothing
-  /// happens.
-  void remove(BlockRef block);
+// A two-level segregated fit store for free blocks.
+//
+// The store starts with small lists that grow linearly for small sizes, which
+// covers [0, ... UNIT_SIZE * EXP_BASE]. For larger sizes, the bits are managed
+// in a 2-D table. One can think of each row containing NUM_STEPS lists. Along
+// the row, the size grows by 2 exponentially; along the column, the size
+// increases by STEP_SIZE linearly.
+//
+// Mathematical layout:
+//   STEP_SIZE = 1 << STEP_SIZE_BITS
+//   NUM_STEPS = 1 << NUM_STEP_BITS
+//   EXP_BASE = STEP_SIZE * NUM_STEPS
+//   LARGE_SIZE_THRESHOLD = UNIT_SIZE * EXP_BASE
+//
+// Visual representation with example parameters:
+//   UNIT_SIZE = 32, STEP_SIZE = 8, NUM_STEPS = 4
+//   EXP_BASE = 32, THRESHOLD = 1024 B (1 KiB)
+//
+// 1. Small Sizes (Linear Array):
+//    Covers [0, ... 1024 B] growing directly by UNIT_SIZE = 32 B
+//   +-------+-------+-------+-------+-------+-----------+---------------+
+//   | [0 B] | [32B] | [64B] | [96B] |  ...  | [992 B]   | [1024 B (Th)] |
+//   +-------+-------+-------+-------+-------+-----------+---------------+
+//
+// 2. Large Sizes (2-D Table):
+//    Rows = FL (Exponential growth), Columns = SL (Linear steps)
+//    One can think of each Row containing NUM_STEPS (4) lists.
+//
+//                       LINEAR INCREASE ALONG COLUMN (SL) --->
+//             +---------------+---------------+---------------+---------------+
+//             |    Col = 0    |    Col = 1    |    Col = 2    |    Col = 3    |
+//             |    (Base)     |   (+25% FL)   |   (+50% FL)   |   (+75% FL)   |
+//   +---------+---------------+---------------+---------------+---------------+
+// E | Row = 0 |    1024 B     |    1280 B     |    1536 B     |    1792 B     |
+// X |(Base 1K)| [1024 - 1279] | [1280 - 1535] | [1536 - 1791] | [1792 - 2047] |
+// P +---------+---------------+---------------+---------------+---------------+
+// O | Row = 1 |    2048 B     |    2560 B     |    3072 B     |    3584 B     |
+// N |(Base 2K)| [2048 - 2559] | [2560 - 3071] | [3072 - 3583] | [3584 - 4095] |
+// E +---------+---------------+---------------+---------------+---------------+
+// N | Row = 2 |    4096 B     |    5120 B     |    6144 B     |    7168 B     |
+// T |(Base 4K)| [4096 - 5119] | [5120 - 6143] | [6144 - 7167] | [7168 - 8191] |
+// I +---------+---------------+---------------+---------------+---------------+
+// A | Row = 3 |    8192 B     |   10240 B     |   12288 B     |   14336 B     |
+// L |(Base 8K)|[8192 - 10239]|[10240 - 12287]|[12288 - 14335]|[14336 - 16383]|
+//   +---------+---------------+---------------+---------------+---------------+
+//
+// Note: For the real implementation, we don't actually store the lists in a
+// 2-D structure. Instead, we flatten the entire 2-D layout into a single
+// flat 1-D array of size TOTAL_BITS (free_lists), and map sizes directly to
+// a continuous 1-D index using size_to_bit_index. The allocation state is
+// tracked compactly in the lookup_table bitmask array.
+template <typename CONFIG> class TLSFFreeStoreImpl {
+protected:
+  static_assert(cpp::has_single_bit(CONFIG::UNIT_SIZE),
+                "unit size must be a power of two");
+  static_assert(CONFIG::NUM_TABLE_ENTRIES > 0,
+                "the lookup table must have at least one entry");
 
-  /// Remove a best-fit free block that can contain the given size when
-  /// allocated. Returns nullptr if there is no such block.
-  BlockRef remove_best_fit(size_t size);
+  static constexpr size_t STEP_SIZE = size_t(1) << CONFIG::STEP_SIZE_BITS;
+  static constexpr size_t NUM_STEPS = size_t(1) << CONFIG::NUM_STEP_BITS;
+  static constexpr size_t EXP_BASE = STEP_SIZE * NUM_STEPS;
+  static constexpr int UNIT_SIZE_LOG2 = cpp::bit_width(CONFIG::UNIT_SIZE) - 1;
+  static constexpr int EXP_BASE_LOG2 =
+      CONFIG::STEP_SIZE_BITS + CONFIG::NUM_STEP_BITS;
+  static constexpr size_t BITS_PER_ENTRY =
+      cpp::numeric_limits<uintptr_t>::digits;
+  static constexpr size_t TOTAL_BITS =
+      CONFIG::NUM_TABLE_ENTRIES * BITS_PER_ENTRY;
+  static constexpr bool USE_TRIE = CONFIG::USE_TRIE_FOR_OVERFLOW_BIN;
 
-private:
+public:
   static constexpr size_t MIN_OUTER_SIZE = align_up(
       BlockRef::HEADER_SIZE + sizeof(FreeList::Node), BlockRef::MIN_ALIGN);
-  static constexpr size_t MIN_LARGE_OUTER_SIZE = align_up(
-      BlockRef::HEADER_SIZE + sizeof(FreeTrie::Node), BlockRef::MIN_ALIGN);
-  static constexpr size_t NUM_SMALL_SIZES =
-      (MIN_LARGE_OUTER_SIZE - MIN_OUTER_SIZE) / BlockRef::MIN_ALIGN;
+  static constexpr size_t MIN_INNER_SIZE =
+      MIN_OUTER_SIZE - BlockRef::HEADER_SIZE + BlockRef::PREV_FIELD_SIZE;
+  static constexpr size_t LINEAR_BINS =
+      (CONFIG::UNIT_SIZE == BlockRef::MIN_ALIGN)
+          ? ((EXP_BASE << UNIT_SIZE_LOG2) - MIN_INNER_SIZE) /
+                    CONFIG::UNIT_SIZE +
+                1
+          : EXP_BASE;
 
+  LIBC_INLINE TLSFFreeStoreImpl() = default;
+  LIBC_INLINE TLSFFreeStoreImpl(const TLSFFreeStoreImpl &other) = delete;
+  LIBC_INLINE TLSFFreeStoreImpl &
+  operator=(const TLSFFreeStoreImpl &other) = delete;
+
+  LIBC_INLINE static constexpr size_t index_to_min_size(size_t index);
+  LIBC_INLINE void set_range(FreeTrie::SizeRange range);
+  LIBC_INLINE void insert(BlockRef block);
+  LIBC_INLINE void remove(BlockRef block);
+  LIBC_INLINE BlockRef remove_best_fit(size_t size) {
+    return find_and_remove_fit(size);
+  }
+  LIBC_INLINE BlockRef find_and_remove_fit(size_t size);
+  LIBC_INLINE static constexpr size_t size_to_bit_index(size_t size);
+
+protected:
   LIBC_INLINE static bool too_small(BlockRef block) {
     return block.outer_size() < MIN_OUTER_SIZE;
   }
-  LIBC_INLINE static bool is_small(BlockRef block) {
-    return block.outer_size() < MIN_LARGE_OUTER_SIZE;
-  }
 
-  FreeList &small_list(BlockRef block);
-  FreeList *find_best_small_fit(size_t size);
+  cpp::array<uintptr_t, CONFIG::NUM_TABLE_ENTRIES> lookup_table{};
+  cpp::array<FreeList, TOTAL_BITS - 1> free_lists{};
+  FreeTrie trie{};
----------------
mysterymath wrote:

Definitely add a note that these are mutually exclusive (right?); would we want to use a union or variant here?

https://github.com/llvm/llvm-project/pull/203415


More information about the libc-commits mailing list