| //===----------------------------------------------------------------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file contains a two-level segregated fit table and mapping helper. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_LIBC_SRC___SUPPORT_TLSF_TABLE_H |
| #define LLVM_LIBC_SRC___SUPPORT_TLSF_TABLE_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/macros/config.h" |
| #include "src/__support/macros/optimization.h" |
| #include "src/__support/math_extras.h" |
| |
| namespace LIBC_NAMESPACE_DECL { |
| |
| /// Default configuration for TLSFFreeStore and TLSFTable. |
| 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 = 3; |
| static constexpr bool USE_TRIE_FOR_OVERFLOW_BIN = true; |
| static constexpr size_t LINEAR_SCAN_LIMIT = 16; |
| }; |
| |
| // A two-level segregated fit occupancy table and mapping helper. |
| // |
| // Rather than mapping raw block sizes directly, the table maps the size delta |
| // over MIN_INNER_SIZE (delta = size - (MIN_INNER_SIZE + 1)). Small allocations |
| // map directly to exact-size linear bins covering Bins 0 through EXP_BASE (33 |
| // linear bins total). Larger allocations are managed in a 2-D table starting at |
| // Bin 33. One can think of each row containing NUM_STEPS lists. Along the row, |
| // the delta grows exponentially by powers of 2; along the column, the delta |
| // increases linearly by STEP_SIZE increments. |
| // |
| // Mathematical layout: |
| // delta = size <= MIN_INNER_SIZE ? 0 : size - (MIN_INNER_SIZE + 1) |
| // STEP_SIZE = 1 << STEP_SIZE_BITS |
| // NUM_STEPS = 1 << NUM_STEP_BITS |
| // EXP_BASE = STEP_SIZE * NUM_STEPS |
| // LINEAR_BINS = EXP_BASE + 1 |
| // LARGE_DELTA_THRESHOLD = UNIT_SIZE * EXP_BASE |
| // |
| // Visual representation with realistic configuration parameters: |
| // MIN_INNER_SIZE = 24 B, UNIT_SIZE = 16, STEP_SIZE = 8, NUM_STEPS = 4 |
| // EXP_BASE = 32, LINEAR_BINS = 33, THRESHOLD = 512 B (Size 536 B) |
| // |
| // 1. Small Sizes (Linear Bins on Delta over MIN_INNER_SIZE = 24 B): |
| // Covers size range [0, ... 536 B] across Bins 0 to 32 (EXP_BASE = 32). |
| // Assuming UNIT_SIZE matches block minimum alignment (step = unit = 16 B), |
| // these linear bins hold blocks of exact inner sizes (e.g., Bin 0 = 24 B, |
| // Bin 1 = 40 B, Bin 2 = 56 B). The byte ranges shown below reflect user |
| // payload sizes that map directly into each exact-fit bin: |
| // +-----------+-------------+-------------+-----+--------------+--------------+ |
| // | Bin 0 | Bin 1 | Bin 2 | ... | Bin 31 | Bin 32 | |
| // | [0..24 B] | [25..40 B] | [41..56 B] | ... | [505..520 B] | [521..536 B] | |
| // | (Delta 0) | (D: 0..15) | (D: 16..31) | ... | (D: 480..495)| (D: 496..511)| |
| // +-----------+-------------+-------------+-----+--------------+--------------+ |
| // |
| // 2. Large Sizes (2-D Table on Delta over MIN_INNER_SIZE for Sizes > 536 B): |
| // Rows = FL (Exponential growth), Columns = SL (Linear steps) |
| // Each cell shows Bin index, exact Block Size range, and Delta (D) range: |
| // |
| // LINEAR INCREASE ALONG COLUMN (SL) ---> |
| // +---------------+---------------+---------------+---------------+ |
| // | Col = 0 | Col = 1 | Col = 2 | Col = 3 | |
| // | (Base) | (+25% Step) | (+50% Step) | (+75% Step) | |
| // +---------+---------------+---------------+---------------+---------------+ |
| // E | Row = 0 | Bin 33 | Bin 34 | Bin 35 | Bin 36 | |
| // X | Base D: | [537..664 B] | [665..792 B] | [793..920 B] | [921..1048 B] | |
| // P | 512 | D:[512..639] | D:[640..767] | D:[768..895] | D:[896..1023] | |
| // +---------+---------------+---------------+---------------+---------------+ |
| // O | Row = 1 | Bin 37 | Bin 38 | Bin 39 | Bin 40 | |
| // N | Base D: |[1049..1304 B] |[1305..1560 B] |[1561..1816 B] |[1817..2072 B] | |
| // E | 1024 |D:[1024..1279] |D:[1280..1535] |D:[1536..1791] |D:[1792..2047] | |
| // +---------+---------------+---------------+---------------+---------------+ |
| // N | Row = 2 | Bin 41 | Bin 42 | Bin 43 | Bin 44 | |
| // T | Base D: |[2073..2584 B] |[2585..3096 B] |[3097..3608 B] |[3609..4120 B] | |
| // I | 2048 |D:[2048..2559] |D:[2560..3071] |D:[3072..3583] |D:[3584..4095] | |
| // +---------+---------------+---------------+---------------+---------------+ |
| // A | Row = 3 | Bin 45 | Bin 46 | Bin 47 | Bin 48 | |
| // L | Base D: |[4121..5144 B] |[5145..6168 B] |[6169..7192 B] |[7193..8216 B] | |
| // | 4096 |D:[4096..5119] |D:[5120..6143] |D:[6144..7167] |D:[7168..8191] | |
| // +---------+---------------+---------------+---------------+---------------+ |
| // |
| // 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_BINS, and map sizes directly to a continuous |
| // 1-D index using size_to_bin. The occupancy state is tracked compactly in |
| // the lookup_table bitmask array. |
| template <typename CONFIG> class TLSFTable { |
| 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"); |
| |
| 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; |
| |
| public: |
| static constexpr size_t TOTAL_BINS = |
| CONFIG::NUM_TABLE_ENTRIES * BITS_PER_ENTRY; |
| static constexpr size_t TOTAL_BITS = TOTAL_BINS; |
| |
| static constexpr size_t MIN_OUTER_SIZE = align_up( |
| BlockRef::HEADER_SIZE + sizeof(FreeList::Node), BlockRef::MIN_ALIGN); |
| // Minimal available size for allocation. |
| static constexpr size_t MIN_INNER_SIZE = |
| MIN_OUTER_SIZE - BlockRef::HEADER_SIZE + BlockRef::PREV_FIELD_SIZE; |
| // Number of bins grows linearly. |
| static constexpr size_t LINEAR_BINS = EXP_BASE + 1; |
| |
| LIBC_INLINE constexpr TLSFTable() = default; |
| |
| LIBC_INLINE static constexpr size_t size_to_bin(size_t size); |
| LIBC_INLINE static constexpr size_t bin_to_min_size(size_t bin); |
| |
| LIBC_INLINE void mark_occupied(size_t bin) { |
| size_t entry_index = bin / BITS_PER_ENTRY; |
| size_t bit_offset = bin % BITS_PER_ENTRY; |
| lookup_table[entry_index] |= uintptr_t(1) << bit_offset; |
| } |
| |
| LIBC_INLINE void mark_vacant(size_t bin) { |
| size_t entry_index = bin / BITS_PER_ENTRY; |
| size_t bit_offset = bin % BITS_PER_ENTRY; |
| lookup_table[entry_index] &= ~(uintptr_t(1) << bit_offset); |
| } |
| |
| LIBC_INLINE bool is_occupied(size_t bin) const { |
| size_t entry_index = bin / BITS_PER_ENTRY; |
| size_t bit_offset = bin % BITS_PER_ENTRY; |
| return (lookup_table[entry_index] & (uintptr_t(1) << bit_offset)) != 0; |
| } |
| |
| LIBC_INLINE size_t find_first_occupied_after(size_t bin) const { |
| if (bin >= TOTAL_BINS - 1) |
| return TOTAL_BINS; |
| |
| size_t target_index = bin + 1; |
| size_t start_entry = target_index / BITS_PER_ENTRY; |
| size_t bit_offset = target_index % BITS_PER_ENTRY; |
| |
| uintptr_t value = lookup_table[start_entry] & (~uintptr_t(0) << bit_offset); |
| if (value != 0) |
| return start_entry * BITS_PER_ENTRY + |
| static_cast<size_t>(cpp::countr_zero(value)); |
| |
| for (size_t i = start_entry + 1; i < CONFIG::NUM_TABLE_ENTRIES; ++i) { |
| value = lookup_table[i]; |
| if (value != 0) |
| return i * BITS_PER_ENTRY + |
| static_cast<size_t>(cpp::countr_zero(value)); |
| } |
| return TOTAL_BINS; |
| } |
| |
| private: |
| cpp::array<uintptr_t, CONFIG::NUM_TABLE_ENTRIES> lookup_table{}; |
| }; |
| |
| template <typename CONFIG> |
| LIBC_INLINE constexpr size_t TLSFTable<CONFIG>::size_to_bin(size_t size) { |
| // Compute bin as delta on top of min_inner_size |
| if (sub_overflow(size, MIN_INNER_SIZE + 1, size)) |
| return 0; |
| |
| if (size < (EXP_BASE << UNIT_SIZE_LOG2)) |
| return (size >> UNIT_SIZE_LOG2) + 1; |
| |
| size_t size_ilog2 = static_cast<size_t>(cpp::bit_width(size) - 1); |
| size_t exp_offset = (size_ilog2 - UNIT_SIZE_LOG2 - EXP_BASE_LOG2 - 1) |
| << CONFIG::NUM_STEP_BITS; |
| size_t step_index = size >> (size_ilog2 - CONFIG::NUM_STEP_BITS); |
| size_t index = LINEAR_BINS + exp_offset + step_index; |
| |
| return index < TOTAL_BINS ? index : TOTAL_BINS - 1; |
| } |
| |
| template <typename CONFIG> |
| LIBC_INLINE constexpr size_t TLSFTable<CONFIG>::bin_to_min_size(size_t bin) { |
| if (bin == 0) |
| return 0; |
| if (bin < LINEAR_BINS) |
| return MIN_INNER_SIZE + 1 + ((bin - 1) << UNIT_SIZE_LOG2); |
| |
| size_t local_index = bin - LINEAR_BINS; |
| size_t exp_index = local_index >> CONFIG::NUM_STEP_BITS; |
| size_t linear_index = local_index & (NUM_STEPS - 1); |
| |
| size_t row_base = (EXP_BASE << exp_index) << UNIT_SIZE_LOG2; |
| size_t step_size = (STEP_SIZE << exp_index) << UNIT_SIZE_LOG2; |
| return MIN_INNER_SIZE + 1 + row_base + linear_index * step_size; |
| } |
| |
| } // namespace LIBC_NAMESPACE_DECL |
| |
| #endif // LLVM_LIBC_SRC___SUPPORT_TLSF_TABLE_H |