#ifndef V8_COMMON_SEGMENTED_TABLE_H_
#define V8_COMMON_SEGMENTED_TABLE_H_
#include "include/v8-internal.h"
#include "src/base/macros.h"
#include "src/common/code-memory-access.h"
namespace v8 {
namespace internal {
* A thread-safe table with a fixed maximum size split into segments.
*
* The table provides thread-safe methods to allocate and free of segments and
* an inline freelist implementation. Allocation and Freeing of entries is
* implemented in subclasses since it depends on if the table is manually
* managed or GCed.
*
* For the purpose of memory management, the table is partitioned into Segments
* (for example 64kb memory chunks) that are grouped together in "Spaces". All
* segments in a space share a freelist, and so entry allocation and garbage
* collection happen on the level of spaces.
*
* The Entry type defines how the freelist is represented. For that, it must
* implement the following methods:
* - void MakeFreelistEntry(uint32_t next_entry_index)
* - uint32_t GetNextFreelistEntry()
*/
template <typename Entry, size_t size>
class V8_EXPORT_PRIVATE SegmentedTable {
protected:
static constexpr bool kIsWriteProtected = Entry::IsWriteProtected;
static constexpr int kEntrySize = sizeof(Entry);
#ifdef V8_TARGET_ARCH_64_BIT
static constexpr bool kUseContiguousMemory = true;
static constexpr size_t kReservationSize = size;
static constexpr size_t kMaxCapacity = kReservationSize / kEntrySize;
#if defined(V8_TARGET_OS_WIN)
static constexpr bool kUseSegmentPool = false;
#else
static constexpr bool kUseSegmentPool = kMinimumOSPageSize <= 16 * KB;
#endif
static constexpr size_t kSegmentSize = kUseSegmentPool ? 16 * KB : 64 * KB;
#else
static constexpr bool kUseContiguousMemory = false;
static constexpr bool kUseSegmentPool = false;
#ifdef V8_TARGET_OS_WIN
static constexpr size_t kSegmentSize = 64 * KB;
#else
static constexpr size_t kSegmentSize = 16 * KB;
#endif
#endif
static_assert(kUseContiguousMemory || !V8_ENABLE_SANDBOX_BOOL);
static constexpr size_t kSegmentPoolSize = 4;
static constexpr uint32_t kSegmentPoolFreeEntry =
std::numeric_limits<uint32_t>::max();
static constexpr size_t kEntriesPerSegment = kSegmentSize / kEntrySize;
static constexpr size_t kAlignment = kSegmentSize;
static constexpr size_t kNumReadOnlySegments = 64 * KB / kSegmentSize;
struct Segment {
public:
explicit Segment(uint32_t number) : number_(number) {}
static Segment At(uint32_t offset);
static Segment Containing(uint32_t entry_index);
uint32_t number() const { return number_; }
uint32_t offset() const { return number_ * kSegmentSize; }
uint32_t first_entry() const { return number_ * kEntriesPerSegment; }
uint32_t last_entry() const {
return first_entry() + kEntriesPerSegment - 1;
}
bool operator<(const Segment& other) const {
return number_ < other.number_;
}
private:
const uint32_t number_;
};
struct FreelistHead {
constexpr FreelistHead() : next_(0), length_(0) {}
constexpr FreelistHead(uint32_t next, uint32_t length)
: next_(next), length_(length) {}
uint32_t next() const { return next_; }
uint32_t length() const { return length_; }
bool is_empty() const { return length_ == 0; }
private:
uint32_t next_;
uint32_t length_;
};
static_assert(std::atomic<FreelistHead>::is_always_lock_free);
SegmentedTable() = default;
SegmentedTable(const SegmentedTable&) = delete;
SegmentedTable& operator=(const SegmentedTable&) = delete;
class WriteIterator {
public:
explicit WriteIterator(Entry* base, uint32_t index);
uint32_t index() const { return index_; }
Entry* operator->() {
DCHECK(!crossed_segment_);
return &base_[index_];
}
Entry& operator*() {
DCHECK(!crossed_segment_);
return base_[index_];
}
WriteIterator& operator++() {
index_++;
#ifdef DEBUG
if (IsAligned(index_, kEntriesPerSegment)) {
crossed_segment_ = true;
}
#endif
return *this;
}
WriteIterator& operator--() {
DCHECK_GT(index_, 0);
#ifdef DEBUG
if (IsAligned(index_, kEntriesPerSegment)) {
crossed_segment_ = true;
}
#endif
index_--;
return *this;
}
private:
Entry* base_;
uint32_t index_;
std::conditional_t<kIsWriteProtected, CFIMetadataWriteScope,
NopRwxMemoryWriteScope>
write_scope_;
#ifdef DEBUG
bool crossed_segment_ = false;
#endif
};
Entry& at(uint32_t index);
const Entry& at(uint32_t index) const;
WriteIterator iter_at(uint32_t index);
bool is_initialized() const;
Address base() const;
std::pair<Segment, FreelistHead> AllocateAndInitializeSegment();
std::optional<std::pair<Segment, FreelistHead>>
TryAllocateAndInitializeSegment();
std::optional<Segment> TryAllocateSegment();
std::optional<Segment> TryGetSegmentFromPool();
std::optional<Segment> FillSegmentsPool(bool return_a_segment);
FreelistHead InitializeFreeList(Segment segment, uint32_t start_offset = 0);
void FreeTableSegment(Segment segment);
void Initialize();
void TearDown();
Entry* base_ = nullptr;
VirtualAddressSpace* vas_ = nullptr;
base::Mutex* segment_pool_grow_mutex_ = nullptr;
std::array<std::atomic<uint32_t>, kSegmentPoolSize> segment_pool_ = {};
uint32_t read_only_segments_used_ = 0;
};
}
}
#endif