Phase v5-3: O(1) path optimization for C6-only v5
- Single TLS segment (eliminates slot search loop) - O(1) page_meta_of() (direct segment range check, no iteration) - __builtin_ctz for O(1) free page finding in bitmap - Simplified free path using page_meta_of() only (no find_page) - Partial limit 1 (minimal list traversal) Performance: - Before (v5-2): 14.7M ops/s - After (v5-3): 38.5M ops/s (+162%) - vs baseline: 44.9M ops/s (-14%) - SEGV: None, stable at ws=800 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -103,60 +103,42 @@ void* small_alloc_fast_v5(size_t size, uint32_t class_idx, SmallHeapCtxV5* ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Helper: Find page containing pointer
|
// Helper: Determine page location in heap lists (Phase v5-3)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static inline int ptr_in_page(const SmallPageMetaV5* page, const uint8_t* ptr) {
|
static inline page_loc_t get_page_location(SmallClassHeapV5* h, SmallPageMetaV5* page,
|
||||||
if (!page || !ptr || !page->segment) return 0;
|
SmallPageMetaV5** prev_out) {
|
||||||
|
|
||||||
SmallSegmentV5* seg = (SmallSegmentV5*)page->segment;
|
|
||||||
uintptr_t page_base = seg->base + ((uintptr_t)page->page_idx * SMALL_SEGMENT_V5_PAGE_SIZE);
|
|
||||||
size_t span = (size_t)page->capacity * SMALL_HEAP_V5_C6_BLOCK_SIZE;
|
|
||||||
|
|
||||||
if ((uintptr_t)ptr < page_base || (uintptr_t)ptr >= page_base + span) return 0;
|
|
||||||
|
|
||||||
// Check alignment
|
|
||||||
size_t off = (uintptr_t)ptr - page_base;
|
|
||||||
return (off % SMALL_HEAP_V5_C6_BLOCK_SIZE) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SmallPageMetaV5* find_page(SmallClassHeapV5* h, const uint8_t* ptr,
|
|
||||||
page_loc_t* loc, SmallPageMetaV5** prev_out) {
|
|
||||||
if (loc) *loc = LOC_NONE;
|
|
||||||
if (prev_out) *prev_out = NULL;
|
if (prev_out) *prev_out = NULL;
|
||||||
if (!h || !ptr) return NULL;
|
if (!h || !page) return LOC_NONE;
|
||||||
|
|
||||||
// Check current
|
// Check current (O(1))
|
||||||
if (h->current && ptr_in_page(h->current, ptr)) {
|
if (h->current == page) {
|
||||||
if (loc) *loc = LOC_CURRENT;
|
return LOC_CURRENT;
|
||||||
return h->current;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check partial list
|
// Check partial list (typically 0-1 pages in v5-3)
|
||||||
SmallPageMetaV5* prev = NULL;
|
SmallPageMetaV5* prev = NULL;
|
||||||
for (SmallPageMetaV5* p = h->partial_head; p; prev = p, p = p->next) {
|
for (SmallPageMetaV5* p = h->partial_head; p; prev = p, p = p->next) {
|
||||||
if (ptr_in_page(p, ptr)) {
|
if (p == page) {
|
||||||
if (loc) *loc = LOC_PARTIAL;
|
|
||||||
if (prev_out) *prev_out = prev;
|
if (prev_out) *prev_out = prev;
|
||||||
return p;
|
return LOC_PARTIAL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check full list
|
// Check full list
|
||||||
prev = NULL;
|
prev = NULL;
|
||||||
for (SmallPageMetaV5* p = h->full_head; p; prev = p, p = p->next) {
|
for (SmallPageMetaV5* p = h->full_head; p; prev = p, p = p->next) {
|
||||||
if (ptr_in_page(p, ptr)) {
|
if (p == page) {
|
||||||
if (loc) *loc = LOC_FULL;
|
|
||||||
if (prev_out) *prev_out = prev;
|
if (prev_out) *prev_out = prev;
|
||||||
return p;
|
return LOC_FULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return LOC_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Phase v5-2: Fast free (C6-only full implementation)
|
// Phase v5-3: Fast free (C6-only O(1) implementation)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void small_free_fast_v5(void* ptr, uint32_t class_idx, SmallHeapCtxV5* ctx) {
|
void small_free_fast_v5(void* ptr, uint32_t class_idx, SmallHeapCtxV5* ctx) {
|
||||||
@ -166,37 +148,21 @@ void small_free_fast_v5(void* ptr, uint32_t class_idx, SmallHeapCtxV5* ctx) {
|
|||||||
|
|
||||||
// C6-only check
|
// C6-only check
|
||||||
if (unlikely(class_idx != SMALL_HEAP_V5_C6_CLASS_IDX)) {
|
if (unlikely(class_idx != SMALL_HEAP_V5_C6_CLASS_IDX)) {
|
||||||
// Fallback to pool v1 for non-C6 classes
|
hak_pool_free(ptr, 0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase v5-3: O(1) segment lookup (no list search)
|
||||||
|
SmallPageMetaV5* page = small_segment_v5_page_meta_of(ptr);
|
||||||
|
if (unlikely(!page)) {
|
||||||
|
// Not in v5 segment, fallback to pool v1
|
||||||
hak_pool_free(ptr, 0, 0);
|
hak_pool_free(ptr, 0, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallClassHeapV5* h = &ctx->cls[SMALL_HEAP_V5_C6_CLASS_IDX];
|
SmallClassHeapV5* h = &ctx->cls[SMALL_HEAP_V5_C6_CLASS_IDX];
|
||||||
|
|
||||||
// Try O(1) segment lookup first (Phase v5-2 optimization)
|
// Push to freelist (O(1))
|
||||||
SmallPageMetaV5* page = small_segment_v5_page_meta_of(ptr);
|
|
||||||
page_loc_t loc = LOC_NONE;
|
|
||||||
SmallPageMetaV5* prev = NULL;
|
|
||||||
|
|
||||||
// If segment lookup failed, search through lists (fallback)
|
|
||||||
if (!page) {
|
|
||||||
page = find_page(h, (const uint8_t*)ptr, &loc, &prev);
|
|
||||||
if (!page) {
|
|
||||||
// Not found in v5 heap, fallback to pool v1
|
|
||||||
hak_pool_free(ptr, 0, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Segment lookup succeeded, determine location in lists
|
|
||||||
if (h->current == page) {
|
|
||||||
loc = LOC_CURRENT;
|
|
||||||
} else {
|
|
||||||
// Search in partial/full lists to get prev pointer
|
|
||||||
find_page(h, (const uint8_t*)ptr, &loc, &prev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push to freelist
|
|
||||||
void* head = page->free_list;
|
void* head = page->free_list;
|
||||||
memcpy(ptr, &head, sizeof(void*));
|
memcpy(ptr, &head, sizeof(void*));
|
||||||
page->free_list = ptr;
|
page->free_list = ptr;
|
||||||
@ -206,50 +172,55 @@ void small_free_fast_v5(void* ptr, uint32_t class_idx, SmallHeapCtxV5* ctx) {
|
|||||||
|
|
||||||
// Handle empty page (used == 0)
|
// Handle empty page (used == 0)
|
||||||
if (page->used == 0) {
|
if (page->used == 0) {
|
||||||
// Unlink from current location
|
// Fast path: if this is current, just keep it
|
||||||
if (loc != LOC_CURRENT) {
|
if (h->current == page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine location and unlink (rare path)
|
||||||
|
SmallPageMetaV5* prev = NULL;
|
||||||
|
page_loc_t loc = get_page_location(h, page, &prev);
|
||||||
|
if (loc != LOC_NONE && loc != LOC_CURRENT) {
|
||||||
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to make it current if we don't have one
|
// Promote to current if empty
|
||||||
if (!h->current) {
|
if (!h->current) {
|
||||||
h->current = page;
|
h->current = page;
|
||||||
page->next = NULL;
|
page->next = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already have current, check if we can keep in partial
|
// Try partial (limit 1)
|
||||||
if (h->current == page) {
|
|
||||||
page->next = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to push to partial list
|
|
||||||
if (h->partial_count < SMALL_HEAP_V5_C6_PARTIAL_LIMIT) {
|
if (h->partial_count < SMALL_HEAP_V5_C6_PARTIAL_LIMIT) {
|
||||||
SMALL_PAGE_V5_PUSH_PARTIAL(h, page);
|
SMALL_PAGE_V5_PUSH_PARTIAL(h, page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Partial list full, retire the page
|
// Retire to cold
|
||||||
small_cold_v5_retire_page(ctx, page);
|
small_cold_v5_retire_page(ctx, page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page is not empty, handle transitions
|
// Page not empty - handle full→partial transition
|
||||||
if (!h->current) {
|
if (h->current != page) {
|
||||||
// No current page, promote this one
|
SmallPageMetaV5* prev = NULL;
|
||||||
if (loc != LOC_CURRENT) {
|
page_loc_t loc = get_page_location(h, page, &prev);
|
||||||
|
if (loc == LOC_FULL && page->free_list) {
|
||||||
|
// Move from full to partial
|
||||||
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
||||||
}
|
if (h->partial_count < SMALL_HEAP_V5_C6_PARTIAL_LIMIT) {
|
||||||
h->current = page;
|
SMALL_PAGE_V5_PUSH_PARTIAL(h, page);
|
||||||
page->next = NULL;
|
} else {
|
||||||
} else if (loc == LOC_FULL && page->free_list) {
|
SMALL_PAGE_V5_PUSH_FULL(h, page);
|
||||||
// Move from full to partial (now has free blocks)
|
}
|
||||||
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
} else if (!h->current) {
|
||||||
if (h->partial_count < SMALL_HEAP_V5_C6_PARTIAL_LIMIT) {
|
// No current, promote this
|
||||||
SMALL_PAGE_V5_PUSH_PARTIAL(h, page);
|
if (loc != LOC_NONE) {
|
||||||
} else {
|
SMALL_PAGE_V5_UNLINK(h, loc, prev, page);
|
||||||
SMALL_PAGE_V5_PUSH_FULL(h, page); // Keep in full if partial limit exceeded
|
}
|
||||||
|
h->current = page;
|
||||||
|
page->next = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,29 +19,22 @@
|
|||||||
// Segment Allocation (Phase v5-2)
|
// Segment Allocation (Phase v5-2)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Thread-local segment list (static allocation to avoid malloc recursion)
|
// Thread-local segment (Phase v5-3: single segment per thread for O(1) lookup)
|
||||||
#define MAX_SEGMENTS_PER_THREAD 4
|
// C6-only v5 uses at most 1 segment per thread - this eliminates slot search
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SmallSegmentV5 seg;
|
SmallSegmentV5 seg;
|
||||||
int in_use;
|
int in_use;
|
||||||
uint32_t used_pages; // Bitmap: which pages are currently in use
|
uint32_t used_pages; // Bitmap: which pages are currently in use
|
||||||
} TLSSegmentSlot;
|
} TLSSegmentSlot;
|
||||||
|
|
||||||
static __thread TLSSegmentSlot g_segment_slots_v5[MAX_SEGMENTS_PER_THREAD];
|
static __thread TLSSegmentSlot g_tls_segment_v5; // Single TLS segment
|
||||||
static __thread int g_last_alloc_slot_v5 = -1; // Last slot we allocated from
|
|
||||||
|
|
||||||
SmallSegmentV5* small_segment_v5_acquire(void) {
|
SmallSegmentV5* small_segment_v5_acquire(void) {
|
||||||
// Find free slot in TLS (avoid malloc to prevent recursion)
|
// Phase v5-3: Single segment per thread - no slot search needed
|
||||||
TLSSegmentSlot* slot = NULL;
|
TLSSegmentSlot* slot = &g_tls_segment_v5;
|
||||||
for (int i = 0; i < MAX_SEGMENTS_PER_THREAD; i++) {
|
|
||||||
if (!g_segment_slots_v5[i].in_use) {
|
|
||||||
slot = &g_segment_slots_v5[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!slot) {
|
if (slot->in_use) {
|
||||||
return NULL; // Out of TLS segment slots
|
return NULL; // Already have a segment (reuse pages instead)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate 2MiB aligned segment via mmap
|
// Allocate 2MiB aligned segment via mmap
|
||||||
@ -138,17 +131,9 @@ void small_segment_v5_release(SmallSegmentV5* seg) {
|
|||||||
// Release the 2MiB backing memory
|
// Release the 2MiB backing memory
|
||||||
munmap((void*)seg->base, SMALL_SEGMENT_V5_SIZE);
|
munmap((void*)seg->base, SMALL_SEGMENT_V5_SIZE);
|
||||||
|
|
||||||
// Mark slot as free (TLS memory is never freed, just reused)
|
// Phase v5-3: Single segment - direct reset
|
||||||
for (int i = 0; i < MAX_SEGMENTS_PER_THREAD; i++) {
|
g_tls_segment_v5.in_use = 0;
|
||||||
if (&g_segment_slots_v5[i].seg == seg) {
|
g_tls_segment_v5.used_pages = 0;
|
||||||
g_segment_slots_v5[i].in_use = 0;
|
|
||||||
g_segment_slots_v5[i].used_pages = 0;
|
|
||||||
if (g_last_alloc_slot_v5 == i) {
|
|
||||||
g_last_alloc_slot_v5 = -1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -156,55 +141,31 @@ void small_segment_v5_release(SmallSegmentV5* seg) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
SmallPageMetaV5* small_segment_v5_alloc_page(void) {
|
SmallPageMetaV5* small_segment_v5_alloc_page(void) {
|
||||||
// Try to reuse existing segment with free pages
|
TLSSegmentSlot* slot = &g_tls_segment_v5;
|
||||||
if (g_last_alloc_slot_v5 >= 0 && g_last_alloc_slot_v5 < MAX_SEGMENTS_PER_THREAD) {
|
|
||||||
TLSSegmentSlot* slot = &g_segment_slots_v5[g_last_alloc_slot_v5];
|
// Phase v5-3: Single segment - direct access, no slot search
|
||||||
// Check if not all pages are used (used_pages != 0xFFFFFFFF for 32 pages)
|
if (slot->in_use && slot->used_pages != 0xFFFFFFFF) {
|
||||||
if (slot->in_use && slot->used_pages != 0xFFFFFFFF) {
|
// Segment has free pages - use __builtin_ctz for O(1) free page find
|
||||||
// This segment has free pages
|
uint32_t free_mask = ~slot->used_pages;
|
||||||
SmallSegmentV5* seg = &slot->seg;
|
if (free_mask) {
|
||||||
for (uint32_t i = 0; i < seg->num_pages; i++) {
|
uint32_t page_idx = (uint32_t)__builtin_ctz(free_mask);
|
||||||
if ((slot->used_pages & (1U << i)) == 0) {
|
slot->used_pages |= (1U << page_idx);
|
||||||
// Found free page
|
return &slot->seg.page_meta[page_idx];
|
||||||
slot->used_pages |= (1U << i);
|
|
||||||
return &seg->page_meta[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search all slots for a segment with free pages
|
// Need new segment
|
||||||
for (int s = 0; s < MAX_SEGMENTS_PER_THREAD; s++) {
|
if (!slot->in_use) {
|
||||||
TLSSegmentSlot* slot = &g_segment_slots_v5[s];
|
SmallSegmentV5* seg = small_segment_v5_acquire();
|
||||||
if (slot->in_use && slot->used_pages != 0xFFFFFFFF) {
|
if (!seg) {
|
||||||
SmallSegmentV5* seg = &slot->seg;
|
return NULL;
|
||||||
for (uint32_t i = 0; i < seg->num_pages; i++) {
|
|
||||||
if ((slot->used_pages & (1U << i)) == 0) {
|
|
||||||
// Found free page
|
|
||||||
slot->used_pages |= (1U << i);
|
|
||||||
g_last_alloc_slot_v5 = s;
|
|
||||||
return &seg->page_meta[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// First page
|
||||||
|
slot->used_pages |= 1U;
|
||||||
|
return &seg->page_meta[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// No free pages in existing segments, allocate new segment
|
return NULL; // Segment full (shouldn't happen with page recycling)
|
||||||
SmallSegmentV5* seg = small_segment_v5_acquire();
|
|
||||||
if (!seg) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark first page as used
|
|
||||||
for (int s = 0; s < MAX_SEGMENTS_PER_THREAD; s++) {
|
|
||||||
if (&g_segment_slots_v5[s].seg == seg) {
|
|
||||||
g_segment_slots_v5[s].used_pages |= 1U; // Mark page 0 as used
|
|
||||||
g_last_alloc_slot_v5 = s;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &seg->page_meta[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void small_segment_v5_free_page(SmallPageMetaV5* page) {
|
void small_segment_v5_free_page(SmallPageMetaV5* page) {
|
||||||
@ -212,22 +173,16 @@ void small_segment_v5_free_page(SmallPageMetaV5* page) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallSegmentV5* seg = (SmallSegmentV5*)page->segment;
|
// Phase v5-3: Single segment - direct bitmap clear
|
||||||
|
TLSSegmentSlot* slot = &g_tls_segment_v5;
|
||||||
// Find the slot and clear the used bit
|
if (slot->in_use && page->segment == &slot->seg) {
|
||||||
for (int s = 0; s < MAX_SEGMENTS_PER_THREAD; s++) {
|
slot->used_pages &= ~(1U << page->page_idx);
|
||||||
if (&g_segment_slots_v5[s].seg == seg) {
|
// Keep segment for reuse even if empty
|
||||||
g_segment_slots_v5[s].used_pages &= ~(1U << page->page_idx);
|
|
||||||
|
|
||||||
// If segment is now empty, we could release it
|
|
||||||
// For now, keep it for reuse
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// O(1) Page Metadata Lookup (Phase v5-2)
|
// O(1) Page Metadata Lookup (Phase v5-3: Single segment, no search)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr) {
|
SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr) {
|
||||||
@ -235,46 +190,35 @@ SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase v5-3: Single segment - direct TLS access, no loop
|
||||||
|
TLSSegmentSlot* slot = &g_tls_segment_v5;
|
||||||
|
if (unlikely(!slot->in_use)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SmallSegmentV5* seg = &slot->seg;
|
||||||
uintptr_t addr = (uintptr_t)ptr;
|
uintptr_t addr = (uintptr_t)ptr;
|
||||||
uintptr_t seg_base = addr & ~(SMALL_SEGMENT_V5_SIZE - 1);
|
uintptr_t seg_base = seg->base;
|
||||||
|
|
||||||
// Search for segment in TLS slots
|
// Check if ptr is within this segment's range
|
||||||
SmallSegmentV5* seg = NULL;
|
if (unlikely(addr < seg_base || addr >= seg_base + SMALL_SEGMENT_V5_SIZE)) {
|
||||||
for (int i = 0; i < MAX_SEGMENTS_PER_THREAD; i++) {
|
|
||||||
if (g_segment_slots_v5[i].in_use) {
|
|
||||||
SmallSegmentV5* candidate = &g_segment_slots_v5[i].seg;
|
|
||||||
if (candidate->base == seg_base) {
|
|
||||||
seg = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unlikely(!seg)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify magic number (Fail-Fast validation)
|
// Compute page index via shift (O(1))
|
||||||
if (unlikely(seg->magic != SMALL_SEGMENT_V5_MAGIC)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute page index via shift
|
|
||||||
size_t page_idx = (addr - seg_base) >> SMALL_SEGMENT_V5_PAGE_SHIFT;
|
size_t page_idx = (addr - seg_base) >> SMALL_SEGMENT_V5_PAGE_SHIFT;
|
||||||
|
|
||||||
// Bounds check
|
// Bounds check (should always pass if within segment)
|
||||||
if (unlikely(page_idx >= seg->num_pages)) {
|
if (unlikely(page_idx >= seg->num_pages)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
SmallPageMetaV5* page = &seg->page_meta[page_idx];
|
SmallPageMetaV5* page = &seg->page_meta[page_idx];
|
||||||
|
|
||||||
// Validate that this page is actually in use (has been allocated)
|
// Validate that this page is actually in use
|
||||||
// Unallocated pages have capacity == 0
|
|
||||||
if (unlikely(page->capacity == 0)) {
|
if (unlikely(page->capacity == 0)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return page metadata
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user