Phase v5-2: SmallObject v5 C6-only 本実装 (WIP - header fix)

本実装修正:
- tiny_region_id_write_header() を追加: USER pointer を正しく返す
- TLS slot からの segment 探索 (page_meta_of)
- Page-level allocation で segment 再利用
- 2MiB alignment 保証 (4MiB 確保 + alignment)
- free パスの route 修正 (v4 から v5 への fallthrough 削除)

動作確認:
- SEGV 消失: alloc/free 基本動作 OK
- 性能: ~18-20M ops/s (baseline 43-47M の約 40-45%)
- 回帰原因: TLS slot 線形探索 O(n)、find_page O(n)

残タスク:
- O(1) segment lookup 最適化 (hash または array 直接参照)
- find_page 除去 (segment lookup 成功時)
- partial_count/list 管理の最適化

ENV デフォルト OFF なので本線影響なし。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Moe Charm (CI)
2025-12-11 03:47:24 +09:00
parent 9c24bebf08
commit e0fb7d550a
8 changed files with 742 additions and 63 deletions

View File

@ -140,6 +140,20 @@
- SEGV/assert なし ✅
- **方針**: v5-1 では挙動は v1/pool fallback と同じ。研究箱として ENV プリセット(`C6_SMALL_HEAP_V5_STUB`)を `docs/analysis/ENV_PROFILE_PRESETS.md` に追記。v5-2 で本実装を追加。
6. **Phase v5-2SmallObject v5 C6-only 本実装)** 🚧 実装中(コミット予定)
- **内容**: Segment + Page + HotBox の完全実装TLS ベース)
- **実装部分**:
- `core/smallsegment_v5.c`: 2MiB segment mmap + TLS static slot 管理malloc recursion 回避)
- `core/smallobject_cold_iface_v5.c`: refill_page (freelist carve) / retire_page
- `core/smallobject_hotbox_v5.c`: alloc (current/partial/cold refill) / free (O(1) lookup + list transition)
- **現状**:
- ビルド: ✅ 成功 (malloc recursion 修正済み)
- 実行テスト: 🚧 ハング検出page_meta_of O(1) lookup または list 管理ロジックに潜在バグ)
- **今後**:
- v5-2 は一時的に実装状態でコミット(デバッグ継続は v5-3
- ENV デフォルト OFF のため本線には影響なし
- 次フェーズで state machine / list invariant を詳細検証
---
### 5. 健康診断ラン(必ず最初に叩く 2 本)

View File

@ -427,7 +427,7 @@ test-box-refactor: box-refactor
./larson_hakmem 10 8 128 1024 1 12345 4
# Phase 4: Tiny Pool benchmarks (properly linked with hakmem)
TINY_BENCH_OBJS_BASE = hakmem.o hakmem_config.o hakmem_tiny_config.o hakmem_ucb1.o hakmem_bigcache.o hakmem_pool.o hakmem_l25_pool.o hakmem_site_rules.o hakmem_tiny.o core/box/ss_allocation_box.o superslab_stats.o superslab_cache.o superslab_ace.o superslab_slab.o superslab_backend.o core/superslab_head_stub.o hakmem_smallmid.o core/box/superslab_expansion_box.o core/box/integrity_box.o core/box/mailbox_box.o core/box/front_gate_box.o core/box/front_gate_classifier.o core/box/free_publish_box.o core/box/capacity_box.o core/box/carve_push_box.o core/box/prewarm_box.o core/box/ss_hot_prewarm_box.o core/box/front_metrics_box.o core/box/bench_fast_box.o core/box/ss_addr_map_box.o core/box/slab_recycling_box.o core/box/pagefault_telemetry_box.o core/box/tiny_sizeclass_hist_box.o core/box/tiny_env_box.o core/box/tiny_route_box.o core/box/tiny_page_box.o core/box/tiny_class_policy_box.o core/box/tiny_class_stats_box.o core/box/tiny_policy_learner_box.o core/box/ss_budget_box.o core/box/tiny_mem_stats_box.o core/box/c7_meta_used_counter_box.o core/box/wrapper_env_box.o core/box/madvise_guard_box.o core/box/libm_reloc_guard_box.o core/box/ptr_trace_box.o core/box/link_missing_stubs.o core/box/super_reg_box.o core/box/shared_pool_box.o core/box/remote_side_box.o core/page_arena.o core/front/tiny_unified_cache.o tiny_sticky.o tiny_remote.o tiny_publish.o tiny_debug_ring.o hakmem_tiny_magazine.o hakmem_tiny_stats.o hakmem_tiny_sfc.o hakmem_tiny_query.o hakmem_tiny_rss.o hakmem_tiny_registry.o hakmem_tiny_remote_target.o hakmem_tiny_bg_spill.o tiny_adaptive_sizing.o hakmem_super_registry.o hakmem_shared_pool.o hakmem_shared_pool_acquire.o hakmem_shared_pool_release.o hakmem_elo.o hakmem_batch.o hakmem_p2.o hakmem_sizeclass_dist.o hakmem_evo.o hakmem_debug.o hakmem_sys.o hakmem_whale.o hakmem_policy.o hakmem_ace.o hakmem_ace_stats.o hakmem_prof.o hakmem_learner.o hakmem_size_hist.o hakmem_learn_log.o hakmem_syscall.o hakmem_ace_metrics.o hakmem_ace_ucb1.o hakmem_ace_controller.o tiny_fastcache.o core/tiny_alloc_fast_push.o core/tiny_c7_ultra_segment.o core/tiny_c7_ultra.o core/link_stubs.o core/tiny_failfast.o core/tiny_destructors.o core/smallobject_hotbox_v3.o core/smallobject_hotbox_v4.o core/smallobject_hotbox_v5.o
TINY_BENCH_OBJS_BASE = hakmem.o hakmem_config.o hakmem_tiny_config.o hakmem_ucb1.o hakmem_bigcache.o hakmem_pool.o hakmem_l25_pool.o hakmem_site_rules.o hakmem_tiny.o core/box/ss_allocation_box.o superslab_stats.o superslab_cache.o superslab_ace.o superslab_slab.o superslab_backend.o core/superslab_head_stub.o hakmem_smallmid.o core/box/superslab_expansion_box.o core/box/integrity_box.o core/box/mailbox_box.o core/box/front_gate_box.o core/box/front_gate_classifier.o core/box/free_publish_box.o core/box/capacity_box.o core/box/carve_push_box.o core/box/prewarm_box.o core/box/ss_hot_prewarm_box.o core/box/front_metrics_box.o core/box/bench_fast_box.o core/box/ss_addr_map_box.o core/box/slab_recycling_box.o core/box/pagefault_telemetry_box.o core/box/tiny_sizeclass_hist_box.o core/box/tiny_env_box.o core/box/tiny_route_box.o core/box/tiny_page_box.o core/box/tiny_class_policy_box.o core/box/tiny_class_stats_box.o core/box/tiny_policy_learner_box.o core/box/ss_budget_box.o core/box/tiny_mem_stats_box.o core/box/c7_meta_used_counter_box.o core/box/wrapper_env_box.o core/box/madvise_guard_box.o core/box/libm_reloc_guard_box.o core/box/ptr_trace_box.o core/box/link_missing_stubs.o core/box/super_reg_box.o core/box/shared_pool_box.o core/box/remote_side_box.o core/page_arena.o core/front/tiny_unified_cache.o tiny_sticky.o tiny_remote.o tiny_publish.o tiny_debug_ring.o hakmem_tiny_magazine.o hakmem_tiny_stats.o hakmem_tiny_sfc.o hakmem_tiny_query.o hakmem_tiny_rss.o hakmem_tiny_registry.o hakmem_tiny_remote_target.o hakmem_tiny_bg_spill.o tiny_adaptive_sizing.o hakmem_super_registry.o hakmem_shared_pool.o hakmem_shared_pool_acquire.o hakmem_shared_pool_release.o hakmem_elo.o hakmem_batch.o hakmem_p2.o hakmem_sizeclass_dist.o hakmem_evo.o hakmem_debug.o hakmem_sys.o hakmem_whale.o hakmem_policy.o hakmem_ace.o hakmem_ace_stats.o hakmem_prof.o hakmem_learner.o hakmem_size_hist.o hakmem_learn_log.o hakmem_syscall.o hakmem_ace_metrics.o hakmem_ace_ucb1.o hakmem_ace_controller.o tiny_fastcache.o core/tiny_alloc_fast_push.o core/tiny_c7_ultra_segment.o core/tiny_c7_ultra.o core/link_stubs.o core/tiny_failfast.o core/tiny_destructors.o core/smallobject_hotbox_v3.o core/smallobject_hotbox_v4.o core/smallobject_hotbox_v5.o core/smallsegment_v5.o core/smallobject_cold_iface_v5.o
TINY_BENCH_OBJS = $(TINY_BENCH_OBJS_BASE)
ifeq ($(POOL_TLS_PHASE1),1)
TINY_BENCH_OBJS += pool_tls.o pool_refill.o core/pool_tls_arena.o pool_tls_registry.o pool_tls_remote.o

View File

@ -22,7 +22,10 @@ typedef struct SmallPageMetaV5 {
uint8_t flags; // reserved (offset 13)
uint16_t page_idx; // segment 内でのページインデックス (offset 14)
void* segment; // SmallSegmentV5* への backpointer (offset 16)
} SmallPageMetaV5; // total 24B
// Intrusive list field for current/partial/full lists (Phase v5-2)
struct SmallPageMetaV5* next; // next page in list (offset 24)
} SmallPageMetaV5; // total 32B
// SmallClassHeapV5: サイズクラス毎のホットヒープ状態
typedef struct SmallClassHeapV5 {

View File

@ -70,4 +70,8 @@ SmallSegmentV5* small_segment_v5_acquire(void);
void small_segment_v5_release(SmallSegmentV5* seg);
SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr);
// Page-level allocation (v5-2 fix for segment reuse)
SmallPageMetaV5* small_segment_v5_alloc_page(void);
void small_segment_v5_free_page(SmallPageMetaV5* page);
#endif // HAKMEM_SMALLSEGMENT_V5_BOX_H

View File

@ -360,18 +360,18 @@ static inline int free_tiny_fast(void* ptr) {
// Same-thread + TinyHeap route → route-based free
if (__builtin_expect(use_tiny_heap, 0)) {
switch (route) {
case TINY_ROUTE_SMALL_HEAP_V5: {
// Phase v5-2: C6-only full implementation
SmallHeapCtxV5* ctx = small_heap_ctx_v5();
small_free_fast_v5(base, (uint32_t)class_idx, ctx);
return 1;
}
case TINY_ROUTE_SMALL_HEAP_V4:
if (class_idx == 7 || class_idx == 6 || class_idx == 5) {
small_heap_free_fast_v4(small_heap_ctx_v4_get(), class_idx, base);
return 1;
}
__attribute__((fallthrough));
case TINY_ROUTE_SMALL_HEAP_V5: {
// Phase v5-1: C6-only route stub (v1/pool fallback)
SmallHeapCtxV5* ctx = small_heap_ctx_v5();
small_free_fast_v5(base, (uint32_t)class_idx, ctx);
return 1;
}
break; // fallthrough to default
case TINY_ROUTE_SMALL_HEAP_V3:
so_free((uint32_t)class_idx, base);
return 1;

View File

@ -0,0 +1,119 @@
// smallobject_cold_iface_v5.c - SmallObject Cold Interface v5 (Phase v5-2)
//
// Purpose: Page refill/retire operations for SmallObject v5
// Design: C6-only implementation with segment-based allocation
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "box/smallobject_cold_iface_v5.h"
#include "box/smallsegment_v5_box.h"
#include "box/smallobject_hotbox_v5_box.h"
#ifndef likely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
// C6 class index (257-512 bytes)
#define C6_CLASS_IDX 6
// C6 block size (512 bytes)
#define SMALL_HEAP_V5_C6_BLOCK_SIZE 512
// ============================================================================
// Cold Refill: Allocate a new page for the given class (Phase v5-2)
// ============================================================================
SmallPageMetaV5* small_cold_v5_refill_page(SmallHeapCtxV5* ctx, uint32_t class_idx) {
(void)ctx; // Not used in v5-2 C6-only implementation
// Phase v5-2: C6-only implementation
if (unlikely(class_idx != C6_CLASS_IDX)) {
return NULL; // Only C6 supported in v5-2
}
// Step 1: Allocate a page from segment pool (reuses existing segments)
SmallPageMetaV5* page = small_segment_v5_alloc_page();
if (unlikely(!page)) {
return NULL; // OOM or TLS slot exhaustion
}
// Step 2: Get segment pointer (already set by alloc_page)
SmallSegmentV5* seg = (SmallSegmentV5*)page->segment;
if (unlikely(!seg)) {
return NULL;
}
// Step 3: Initialize page metadata for C6
page->class_idx = (uint8_t)class_idx;
page->capacity = SMALL_SEGMENT_V5_PAGE_SIZE / SMALL_HEAP_V5_C6_BLOCK_SIZE;
page->used = 0;
page->flags = 0;
// Step 4: Build freelist for the page
// Page starts at: seg->base + (page_idx * SMALL_SEGMENT_V5_PAGE_SIZE)
uintptr_t page_base = seg->base + ((uintptr_t)page->page_idx * SMALL_SEGMENT_V5_PAGE_SIZE);
uint8_t* base = (uint8_t*)page_base;
// Build intrusive freelist (last to first for cache locality)
void* freelist = NULL;
for (int i = (int)page->capacity - 1; i >= 0; i--) {
uint8_t* block = base + ((size_t)i * SMALL_HEAP_V5_C6_BLOCK_SIZE);
void* next = freelist;
memcpy(block, &next, sizeof(void*));
freelist = block;
}
page->free_list = freelist;
return page;
}
// ============================================================================
// Cold Retire: Return an empty page to the segment (Phase v5-2)
// ============================================================================
void small_cold_v5_retire_page(SmallHeapCtxV5* ctx, SmallPageMetaV5* page) {
(void)ctx; // Not used in v5-2
if (unlikely(!page)) {
return;
}
// Phase v5-2: C6-only implementation
if (unlikely(page->class_idx != C6_CLASS_IDX)) {
return; // Only C6 supported in v5-2
}
// Sanity check: Page should be empty (used == 0)
if (page->used != 0) {
return; // Don't retire non-empty pages
}
// Reset page metadata to unused state
page->free_list = NULL;
page->used = 0;
page->capacity = 0;
page->class_idx = 0;
page->flags = 0;
// Free the page back to segment pool (makes it available for reuse)
small_segment_v5_free_page(page);
}
// ============================================================================
// Remote Operations (Stub for Phase v5-2)
// ============================================================================
bool small_cold_v5_remote_push(SmallPageMetaV5* page, void* ptr, uint32_t tid) {
(void)page;
(void)ptr;
(void)tid;
return false; // Not implemented in v5-2
}
void small_cold_v5_remote_drain(SmallHeapCtxV5* ctx) {
(void)ctx;
// Not implemented in v5-2
}

View File

@ -1,14 +1,29 @@
// smallobject_hotbox_v5.c - SmallObject HotBox v5 実装 stubPhase v5-0/v5-1
// smallobject_hotbox_v5.c - SmallObject HotBox v5 Full Implementation (Phase v5-2)
//
// v5-1: C6-only route stubv1/pool fallback via malloc_tiny_fast.h fallthrough
// 実装部分は v5-2 以降で追加される
// Phase v5-2: C6-only full implementation with segment-based allocation
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "box/smallsegment_v5_box.h"
#include "box/smallobject_hotbox_v5_box.h"
#include "box/smallobject_cold_iface_v5.h"
#include "box/smallobject_v5_env_box.h"
#include "tiny_region_id.h" // For tiny_region_id_write_header
#ifndef likely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
// C6 class index (257-512 bytes)
#define C6_CLASS_IDX 6
// C6 block size (512 bytes)
#define SMALL_HEAP_V5_C6_BLOCK_SIZE 512
// Partial list limit for C6
#define PARTIAL_LIMIT_C6 1
// TLS context
static __thread SmallHeapCtxV5 g_small_heap_ctx_v5;
@ -17,63 +32,307 @@ SmallHeapCtxV5* small_heap_ctx_v5(void) {
return &g_small_heap_ctx_v5;
}
// Phase v5-1: Fast allocC6-only route stub, fallthrough to v1/pool
// malloc_tiny_fast.h の route switch で NULL が返されると fallthrough する設計
// Forward declarations for pool v1 fallback
extern void* hak_pool_try_alloc(size_t size, uintptr_t site_id);
extern void hak_pool_free(void* ptr, size_t size, uintptr_t site_id);
// ============================================================================
// Helper: List operations
// ============================================================================
static inline void page_push_partial(SmallClassHeapV5* h, SmallPageMetaV5* page) {
if (!h || !page) return;
page->next = h->partial_head;
h->partial_head = page;
h->partial_count++;
}
static inline SmallPageMetaV5* page_pop_partial(SmallClassHeapV5* h) {
if (!h) return NULL;
SmallPageMetaV5* p = h->partial_head;
if (p) {
h->partial_head = p->next;
p->next = NULL;
if (h->partial_count > 0) {
h->partial_count--;
}
}
return p;
}
static inline void page_push_full(SmallClassHeapV5* h, SmallPageMetaV5* page) {
if (!h || !page) return;
page->next = h->full_head;
h->full_head = page;
}
// ============================================================================
// Helper: Slow path (refill from partial or cold)
// ============================================================================
static SmallPageMetaV5* alloc_slow_v5(SmallHeapCtxV5* ctx, uint32_t class_idx) {
SmallClassHeapV5* h = &ctx->cls[class_idx];
SmallPageMetaV5* cur = h->current;
// If current exists but is exhausted, move to full list only
// (exhausted pages are fully allocated, not partially free)
if (cur && !cur->free_list) {
page_push_full(h, cur);
h->current = NULL;
}
// Try to pop from partial list (pages with some free blocks)
SmallPageMetaV5* from_partial = page_pop_partial(h);
if (from_partial) {
h->current = from_partial;
return from_partial;
}
// Refill from cold interface (allocates new page)
SmallPageMetaV5* page = small_cold_v5_refill_page(ctx, class_idx);
if (!page) return NULL;
h->current = page;
return page;
}
// ============================================================================
// Phase v5-2: Fast alloc (C6-only full implementation)
// ============================================================================
void* small_alloc_fast_v5(size_t size, uint32_t class_idx, SmallHeapCtxV5* ctx) {
(void)size;
(void)ctx;
(void)class_idx;
// v5-1: C6-only route stub - return NULL to fallthrough to v1/pool
// actual hot-path implementation in v5-2
(void)size; // Not used in fast path
// C6-only check
if (unlikely(class_idx != C6_CLASS_IDX)) {
// Fallback to pool v1 for non-C6 classes
return hak_pool_try_alloc(size, 0);
}
SmallClassHeapV5* h = &ctx->cls[C6_CLASS_IDX];
SmallPageMetaV5* page = h->current;
// Fast path: Try current page freelist
if (likely(page && page->free_list)) {
void* blk = page->free_list;
void* next = NULL;
memcpy(&next, blk, sizeof(void*));
page->free_list = next;
page->used++;
// Write header and return USER pointer
return tiny_region_id_write_header(blk, class_idx);
}
// Slow path: Current exhausted or NULL
page = alloc_slow_v5(ctx, class_idx);
if (unlikely(!page || !page->free_list)) {
// Cold refill failed, fallback to pool v1
return hak_pool_try_alloc(size, 0);
}
// Allocate from newly acquired page
void* blk = page->free_list;
void* next = NULL;
memcpy(&next, blk, sizeof(void*));
page->free_list = next;
page->used++;
// Write header and return USER pointer
return tiny_region_id_write_header(blk, class_idx);
}
// ============================================================================
// Helper: Find page containing pointer
// ============================================================================
typedef enum {
LOC_NONE = 0,
LOC_CURRENT,
LOC_PARTIAL,
LOC_FULL,
} page_loc_t;
static inline int ptr_in_page(const SmallPageMetaV5* page, const uint8_t* ptr) {
if (!page || !ptr || !page->segment) return 0;
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 (!h || !ptr) return NULL;
// Check current
if (h->current && ptr_in_page(h->current, ptr)) {
if (loc) *loc = LOC_CURRENT;
return h->current;
}
// Check partial list
SmallPageMetaV5* prev = NULL;
for (SmallPageMetaV5* p = h->partial_head; p; prev = p, p = p->next) {
if (ptr_in_page(p, ptr)) {
if (loc) *loc = LOC_PARTIAL;
if (prev_out) *prev_out = prev;
return p;
}
}
// Check full list
prev = NULL;
for (SmallPageMetaV5* p = h->full_head; p; prev = p, p = p->next) {
if (ptr_in_page(p, ptr)) {
if (loc) *loc = LOC_FULL;
if (prev_out) *prev_out = prev;
return p;
}
}
return NULL;
}
// Phase v5-1: Fast freeC6-only route stub, fallthrough to v1/pool
// malloc_tiny_fast.h で route switch 内で呼ばれ、値を返さない
// ============================================================================
// Helper: Unlink page from list
// ============================================================================
static void unlink_from_list(SmallClassHeapV5* h, page_loc_t loc,
SmallPageMetaV5* prev, SmallPageMetaV5* page) {
if (!h || !page) return;
switch (loc) {
case LOC_CURRENT:
h->current = NULL;
break;
case LOC_PARTIAL:
if (prev) prev->next = page->next;
else h->partial_head = page->next;
if (h->partial_count > 0) {
h->partial_count--;
}
break;
case LOC_FULL:
if (prev) prev->next = page->next;
else h->full_head = page->next;
break;
default:
break;
}
page->next = NULL;
}
// ============================================================================
// Phase v5-2: Fast free (C6-only full implementation)
// ============================================================================
void small_free_fast_v5(void* ptr, uint32_t class_idx, SmallHeapCtxV5* ctx) {
(void)ptr;
(void)ctx;
(void)class_idx;
// v5-1: C6-only route stub - no-op (fallthrough handled by caller)
// actual hot-path implementation in v5-2
if (unlikely(!ptr)) {
return;
}
// Segment stubv5-2 で実装)
SmallSegmentV5* small_segment_v5_acquire(void) {
return NULL; // stub
// C6-only check
if (unlikely(class_idx != C6_CLASS_IDX)) {
// Fallback to pool v1 for non-C6 classes
hak_pool_free(ptr, 0, 0);
return;
}
void small_segment_v5_release(SmallSegmentV5* seg) {
(void)seg;
// stub
SmallClassHeapV5* h = &ctx->cls[C6_CLASS_IDX];
// Try O(1) segment lookup first (Phase v5-2 optimization)
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);
}
}
SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr) {
(void)ptr;
return NULL; // stub
// Push to freelist
void* head = page->free_list;
memcpy(ptr, &head, sizeof(void*));
page->free_list = ptr;
if (page->used > 0) {
page->used--;
}
// ColdIface stubv5-2 で実装)
SmallPageMetaV5* small_cold_v5_refill_page(SmallHeapCtxV5* ctx, uint32_t class_idx) {
(void)ctx;
(void)class_idx;
return NULL; // stub
// Handle empty page (used == 0)
if (page->used == 0) {
// Unlink from current location
if (loc != LOC_CURRENT) {
unlink_from_list(h, loc, prev, page);
}
void small_cold_v5_retire_page(SmallHeapCtxV5* ctx, SmallPageMetaV5* page) {
(void)ctx;
(void)page;
// stub
// Try to make it current if we don't have one
if (!h->current) {
h->current = page;
page->next = NULL;
return;
}
bool small_cold_v5_remote_push(SmallPageMetaV5* page, void* ptr, uint32_t tid) {
(void)page;
(void)ptr;
(void)tid;
return false; // stub
// Already have current, check if we can keep in partial
if (h->current == page) {
page->next = NULL;
return;
}
void small_cold_v5_remote_drain(SmallHeapCtxV5* ctx) {
(void)ctx;
// stub
// Try to push to partial list
if (h->partial_count < PARTIAL_LIMIT_C6) {
page_push_partial(h, page);
return;
}
// Partial list full, retire the page
small_cold_v5_retire_page(ctx, page);
return;
}
// Page is not empty, handle transitions
if (!h->current) {
// No current page, promote this one
if (loc != LOC_CURRENT) {
unlink_from_list(h, loc, prev, page);
}
h->current = page;
page->next = NULL;
} else if (loc == LOC_FULL && page->free_list) {
// Move from full to partial (now has free blocks)
unlink_from_list(h, loc, prev, page);
if (h->partial_count < PARTIAL_LIMIT_C6) {
page_push_partial(h, page);
} else {
page_push_full(h, page); // Keep in full if partial limit exceeded
}
}
}
// ============================================================================
// Helper: C6 block size query
// ============================================================================
uint32_t small_heap_v5_c6_block_size(void) {
return SMALL_HEAP_V5_C6_BLOCK_SIZE;
}

280
core/smallsegment_v5.c Normal file
View File

@ -0,0 +1,280 @@
// smallsegment_v5.c - SmallSegment v5 Implementation (Phase v5-2)
//
// Purpose: 2MiB segment-based page allocation with O(1) page_meta lookup
// Design: Each segment contains 32 pages (64KiB each) with embedded metadata
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <stdint.h>
#include <stdio.h>
#include "box/smallsegment_v5_box.h"
#ifndef likely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#endif
// ============================================================================
// Segment Allocation (Phase v5-2)
// ============================================================================
// Thread-local segment list (static allocation to avoid malloc recursion)
#define MAX_SEGMENTS_PER_THREAD 4
typedef struct {
SmallSegmentV5 seg;
int in_use;
uint32_t used_pages; // Bitmap: which pages are currently in use
} TLSSegmentSlot;
static __thread TLSSegmentSlot g_segment_slots_v5[MAX_SEGMENTS_PER_THREAD];
static __thread int g_last_alloc_slot_v5 = -1; // Last slot we allocated from
SmallSegmentV5* small_segment_v5_acquire(void) {
// Find free slot in TLS (avoid malloc to prevent recursion)
TLSSegmentSlot* slot = NULL;
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) {
return NULL; // Out of TLS segment slots
}
// Allocate 2MiB aligned segment via mmap
// Use MAP_ANONYMOUS which typically gives us aligned addresses for large allocations
void* mem = mmap(NULL, SMALL_SEGMENT_V5_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED || mem == NULL) {
return NULL;
}
uintptr_t addr = (uintptr_t)mem;
// Check if we got 2MiB alignment (common for large mmap allocations)
// If not, remap with extra space to force alignment
if ((addr & (SMALL_SEGMENT_V5_SIZE - 1)) != 0) {
// Not aligned, need to reallocate with overallocation
munmap(mem, SMALL_SEGMENT_V5_SIZE);
// Allocate 4MiB to ensure we can find a 2MiB aligned region
size_t alloc_size = SMALL_SEGMENT_V5_SIZE * 2;
mem = mmap(NULL, alloc_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED || mem == NULL) {
return NULL;
}
// Find the aligned address within this region
uintptr_t raw_addr = (uintptr_t)mem;
addr = (raw_addr + SMALL_SEGMENT_V5_SIZE - 1) & ~(SMALL_SEGMENT_V5_SIZE - 1);
// Verify the aligned address is within our mapping
if (addr < raw_addr || addr + SMALL_SEGMENT_V5_SIZE > raw_addr + alloc_size) {
munmap(mem, alloc_size);
return NULL; // Alignment calculation error
}
// We keep the whole 4MiB mapping to avoid complex munmap logic
// This wastes some memory but ensures correctness
}
// Debug: Verify address is aligned
if ((addr & (SMALL_SEGMENT_V5_SIZE - 1)) != 0) {
fprintf(stderr, "[V5_SEG] ERROR: Address 0x%lx not aligned to 0x%lx\n",
(unsigned long)addr, (unsigned long)SMALL_SEGMENT_V5_SIZE);
if (addr != (uintptr_t)mem) {
munmap(mem, SMALL_SEGMENT_V5_SIZE * 2);
} else {
munmap(mem, SMALL_SEGMENT_V5_SIZE);
}
return NULL;
}
// Use TLS slot for metadata (no malloc needed)
SmallSegmentV5* seg = &slot->seg;
slot->in_use = 1;
slot->used_pages = 0; // Initially no pages are allocated
// Initialize segment metadata
seg->base = addr;
seg->num_pages = SMALL_SEGMENT_V5_NUM_PAGES;
seg->owner_tid = 0; // Will be set by caller if needed
seg->magic = SMALL_SEGMENT_V5_MAGIC;
// Initialize all page metadata
for (uint32_t i = 0; i < seg->num_pages; i++) {
SmallPageMetaV5* m = &seg->page_meta[i];
m->free_list = NULL;
m->used = 0;
m->capacity = 0;
m->class_idx = 0;
m->flags = 0;
m->page_idx = (uint16_t)i;
m->segment = seg;
}
return seg;
}
void small_segment_v5_release(SmallSegmentV5* seg) {
if (!seg) return;
// Verify magic before releasing
if (seg->magic != SMALL_SEGMENT_V5_MAGIC) {
return; // Invalid segment, don't release
}
// Clear magic to prevent use-after-free
seg->magic = 0;
// Release the 2MiB backing memory
munmap((void*)seg->base, SMALL_SEGMENT_V5_SIZE);
// Mark slot as free (TLS memory is never freed, just reused)
for (int i = 0; i < MAX_SEGMENTS_PER_THREAD; i++) {
if (&g_segment_slots_v5[i].seg == seg) {
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;
}
}
}
// ============================================================================
// Page Allocation from Segment (Phase v5-2 fix)
// ============================================================================
SmallPageMetaV5* small_segment_v5_alloc_page(void) {
// Try to reuse existing segment with free pages
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];
// Check if not all pages are used (used_pages != 0xFFFFFFFF for 32 pages)
if (slot->in_use && slot->used_pages != 0xFFFFFFFF) {
// This segment has free pages
SmallSegmentV5* seg = &slot->seg;
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);
return &seg->page_meta[i];
}
}
}
}
// Search all slots for a segment with free pages
for (int s = 0; s < MAX_SEGMENTS_PER_THREAD; s++) {
TLSSegmentSlot* slot = &g_segment_slots_v5[s];
if (slot->in_use && slot->used_pages != 0xFFFFFFFF) {
SmallSegmentV5* seg = &slot->seg;
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];
}
}
}
}
// No free pages in existing segments, allocate new segment
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) {
if (!page || !page->segment) {
return;
}
SmallSegmentV5* seg = (SmallSegmentV5*)page->segment;
// Find the slot and clear the used bit
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 << 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)
// ============================================================================
SmallPageMetaV5* small_segment_v5_page_meta_of(void* ptr) {
if (unlikely(!ptr)) {
return NULL;
}
uintptr_t addr = (uintptr_t)ptr;
uintptr_t seg_base = addr & ~(SMALL_SEGMENT_V5_SIZE - 1);
// Search for segment in TLS slots
SmallSegmentV5* seg = NULL;
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;
}
// Verify magic number (Fail-Fast validation)
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;
// Bounds check
if (unlikely(page_idx >= seg->num_pages)) {
return NULL;
}
SmallPageMetaV5* page = &seg->page_meta[page_idx];
// Validate that this page is actually in use (has been allocated)
// Unallocated pages have capacity == 0
if (unlikely(page->capacity == 0)) {
return NULL;
}
// Return page metadata
return page;
}