Files
hakmem/docs/analysis/TLS_SLL_ARCHITECTURE_INVESTIGATION.md

1350 lines
43 KiB
Markdown
Raw Normal View History

Tiny Pool redesign: P0.1, P0.3, P1.1, P1.2 - Out-of-band class_idx lookup This commit implements the first phase of Tiny Pool redesign based on ChatGPT architecture review. The goal is to eliminate Header/Next pointer conflicts by moving class_idx lookup out-of-band (to SuperSlab metadata). ## P0.1: C0(8B) class upgraded to 16B - Size table changed: {16,32,64,128,256,512,1024,2048} (8 classes) - LUT updated: 1..16 → class 0, 17..32 → class 1, etc. - tiny_next_off: C0 now uses offset 1 (header preserved) - Eliminates edge cases for 8B allocations ## P0.3: Slab reuse guard Box (tls_slab_reuse_guard_box.h) - New Box for draining TLS SLL before slab reuse - ENV gate: HAKMEM_TINY_SLAB_REUSE_GUARD=1 - Prevents stale pointers when slabs are recycled - Follows Box theory: single responsibility, minimal API ## P1.1: SuperSlab class_map addition - Added uint8_t class_map[SLABS_PER_SUPERSLAB_MAX] to SuperSlab - Maps slab_idx → class_idx for out-of-band lookup - Initialized to 255 (UNASSIGNED) on SuperSlab creation - Set correctly on slab initialization in all backends ## P1.2: Free fast path uses class_map - ENV gate: HAKMEM_TINY_USE_CLASS_MAP=1 - Free path can now get class_idx from class_map instead of Header - Falls back to Header read if class_map returns invalid value - Fixed Legacy Backend dynamic slab initialization bug ## Documentation added - HAKMEM_ARCHITECTURE_OVERVIEW.md: 4-layer architecture analysis - TLS_SLL_ARCHITECTURE_INVESTIGATION.md: Root cause analysis - PTR_LIFECYCLE_TRACE_AND_ROOT_CAUSE_ANALYSIS.md: Pointer tracking - TINY_REDESIGN_CHECKLIST.md: Implementation roadmap (P0-P3) ## Test results - Baseline: 70% success rate (30% crash - pre-existing issue) - class_map enabled: 70% success rate (same as baseline) - Performance: ~30.5M ops/s (unchanged) ## Next steps (P1.3, P2, P3) - P1.3: Add meta->active for accurate TLS/freelist sync - P2: TLS SLL redesign with Box-based counting - P3: Complete Header out-of-band migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 13:42:39 +09:00
# TLS SLL アーキテクチャ改善と今後の開発方針
**調査日**: 2025-11-28
**対象**: Larson ベンチマーク double-free バグの根本原因と包括的なアーキテクチャ改善
---
## エグゼクティブサマリー
### 根本原因の特定
Larson ベンチマークでの double-free バグの根本原因は、**TLS SLL と `meta->used` の同期不整合**です:
1. **TLS SLL 操作時に `meta->used` が更新されない**
- `tls_sll_push()`: ポインタを TLS SLL に追加するが `meta->used` は変更なし
- `tls_sll_pop()`: ポインタを TLS SLL から取り出すが `meta->used` は変更なし
- Drain 時のみ: `tiny_free_local_box()` 経由で `meta->used--` される
2. **Slab 再利用時の TLS SLL クリア漏れ**
- Slab が `meta->used == 0` で empty と判定される
- しかし TLS SLL には古いポインタが残っている
- Slab が再初期化されて別のアドレス範囲で使われる
- 古いポインタが再度 allocate されて重複検出
3. **既知のバグ(既に修正済み)**
- TLS Drain pushback バグ: `tls_sll_drain_box.h:148-162` (commit c2f104618 で修正)
- ポインタが TLS SLL の 2 箇所に存在する状態を作り出していた
### 影響範囲
- **確実に影響**: Larson ベンチマーク(マルチスレッド、高頻度 alloc/free
- **潜在的影響**: 本番環境での長時間稼働時のメモリリーク・重複 free
- **性能への影響**: 現在の periodic drain (2048 frees ごと) では不十分な可能性
---
## Part 1: 現状分析
### 1.1 TLS SLL の全体構造
#### 主要ファイル
| ファイル | 役割 | 行数 |
|---------|------|------|
| `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h` | TLS SLL のコア実装 (push/pop/splice) | 753行 |
| `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h` | Periodic drain (Option B) | 270行 |
| `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h` | Fast free path (TLS SLL への push) | 370行 |
| `/mnt/workdisk/public_share/hakmem/core/box/free_local_box.c` | Slow free path (`meta->used--`) | 210行 |
#### TLS SLL の設計
```c
// TLS SLL 構造体 (hakmem_tiny.c で定義)
typedef struct {
void* head; // リストの先頭ポインタ (BASE pointer)
uint32_t count; // リスト内の要素数
} TinyTLSSLL;
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES]; // クラスごとに1つ
```
**重要な設計原則**:
- すべてのポインタは **BASE ポインタ** (user pointer - 1 byte)
- クラス 0 と 7 は `next_offset = 0` (header を上書きしない)
- クラス 1-6 は `next_offset = 1` (header を書き戻す)
### 1.2 `meta->used` の更新箇所
#### Increment 箇所 (alloc 時)
| ファイル | 行番号 | 条件 |
|---------|-------|------|
| `tiny_superslab_alloc.inc.h` | 30, 102, 146, 323, 363, 387 | 線形 carve / freelist pop |
| `hakmem_tiny_refill_p0.inc.h` | 126, 131, 200 | バッチ refill |
| `hakmem_tiny_tls_ops.h` | 93, 136 | TLS slab refill |
| `slab_handle.h` | 313 | SlabHandle 経由の操作 |
#### Decrement 箇所 (free 時)
| ファイル | 行番号 | 条件 |
|---------|-------|------|
| `free_local_box.c` | 180 | **唯一の decrement 箇所** |
| `hakmem_tiny_bg_spill.c` | 72, 81 | バックグラウンド spill |
| `hakmem_tiny_tls_ops.h` | 198 | TLS spill |
| `slab_handle.h` | 276 | SlabHandle 経由の free |
**重要な発見**: `meta->used` の decrement は **`tiny_free_local_box()` を経由した場合のみ** 実行される。
### 1.3 ポインタのライフサイクル
```
[状態遷移図]
CARVED (meta->used++)
|
v
ALLOCATED (ユーザーが使用中)
|
v (free 呼び出し)
|
+---> [FAST PATH] TLS SLL に push (meta->used は変更なし) ----+
| |
| v
| (periodic drain)
| |
+---> [SLOW PATH] tiny_free_local_box() --------------------+
|
v
FREELIST (meta->used--, freelist に追加)
|
v
(再利用時)
|
v
ALLOCATED
```
**問題点**:
- Fast path (99% のケース) では `meta->used` が更新されない
- Periodic drain (2048 frees ごと) まで `meta->used` は高いまま
- この間、Slab は "full" に見える → 再利用されない → メモリリーク
### 1.4 Slab 再利用のコードパス
#### Empty 判定条件
```c
// ss_hot_cold_box.h:37-39
static inline bool ss_is_slab_empty(const TinySlabMeta* meta) {
return (meta->capacity > 0 && meta->used == 0);
}
```
#### Empty マーク
```c
// free_local_box.c:183-202
if (meta->used == 0) {
// Slab became EMPTY → mark for highest-priority reuse
ss_mark_slab_empty(ss, slab_idx);
fprintf(stderr, "[FREE_LOCAL_BOX] EMPTY detected: cls=%u ss=%p slab=%d\n",
cls, (void*)ss, slab_idx);
}
```
#### Slab 再利用フロー
1. `shared_pool_acquire_slab()` (hakmem_shared_pool.c:700-1300)
- **Stage 1**: EMPTY slots を優先的に再利用 (empty_mask をスキャン)
- **Stage 2**: UNUSED slots を探す
- **Stage 3**: 新しい SuperSlab を割り当て
2. `superslab_init_slab()` (初期化時)
- `meta->used = 0`
- `meta->freelist = NULL`
- `meta->class_idx = class_idx` を設定
**重要な問題**: Slab が EMPTY と判定されて再初期化される時、**TLS SLL はクリアされない**。
### 1.5 既存の診断機能
#### 環境変数ゲート
| 環境変数 | 機能 | ファイル |
|---------|------|---------|
| `HAKMEM_TINY_SLL_DIAG` | TLS SLL の詳細診断 | tls_sll_box.h:118 |
| `HAKMEM_TINY_SLL_DRAIN_ENABLE` | Periodic drain の有効/無効 | tls_sll_drain_box.h:40 |
| `HAKMEM_TINY_SLL_DRAIN_INTERVAL` | Drain 間隔 (デフォルト 2048) | tls_sll_drain_box.h:54 |
| `HAKMEM_TINY_SLL_DRAIN_DEBUG` | Drain の詳細ログ | tls_sll_drain_box.h:121 |
| `HAKMEM_TINY_SLL_SAFEHEADER` | Header 上書き防止モード | tls_sll_box.h:336 |
| `HAKMEM_TINY_LARSON_FIX` | クロススレッド free 検出 | tiny_free_fast_v2.inc.h:189 |
#### 重複チェック
```c
// tls_sll_box.h:372-402 (debug ビルドのみ)
#if !HAKMEM_BUILD_RELEASE
void* scan = g_tls_sll[class_idx].head;
uint32_t scanned = 0;
const uint32_t limit = 256; // 最大 256 要素をスキャン
while (scan && scanned < limit) {
if (scan == ptr) {
fprintf(stderr, "[TLS_SLL_PUSH_DUP] cls=%d ptr=%p ...\n");
abort(); // 重複検出時は即座に abort
}
// ...
}
#endif
```
---
## Part 2: 修正案の設計
### 2.1 即座のバグ修正(最小変更)
#### 修正 A: TLS SLL クリア on Slab Reuse
**ファイル**: `/mnt/workdisk/public_share/hakmem/core/tiny_superslab_alloc.inc.h`
**場所**: `superslab_refill()` 関数内 (行 192-265)
**問題**: Slab が再初期化される時、TLS SLL に古いポインタが残る。
**修正案**:
```c
// superslab_refill() 内 (行 243 の後に追加)
// CRITICAL FIX: Clear TLS SLL for this class on slab reuse
// Problem: When slab is recycled (EMPTY → reinitialized), old pointers
// remain in TLS SLL, causing double-free on next allocation
// Solution: Drain TLS SLL to ensure no stale pointers remain
#if !HAKMEM_BUILD_RELEASE
{
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES];
uint32_t old_count = g_tls_sll[class_idx].count;
if (old_count > 0) {
// Drain ALL blocks from TLS SLL to freelist (slow but safe)
extern uint32_t tiny_tls_sll_drain(int class_idx, uint32_t batch_size);
uint32_t drained = tiny_tls_sll_drain(class_idx, 0); // 0 = drain all
fprintf(stderr, "[SUPERSLAB_REFILL_TLS_DRAIN] cls=%d drained=%u from TLS SLL\n",
class_idx, drained);
}
}
#endif
```
**影響**:
- **本番ビルド**: 変更なし (debug only)
- **開発ビルド**: Slab refill 時に TLS SLL を強制的に drain
- **性能**: 無視できる (refill は低頻度、O(count) の drain)
**副作用**:
- なし(防御的コード、性能への影響は最小)
---
#### 修正 B: Drain 間隔の動的調整
**ファイル**: `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
**場所**: `tls_sll_drain_get_interval()` (行 54-73)
**問題**: 現在の固定間隔 (2048 frees) では、ワークロードによっては不十分。
**修正案**:
```c
// Dynamic drain interval based on workload
static inline uint32_t tls_sll_drain_get_interval_dynamic(int class_idx) {
static uint32_t g_drain_interval_base = 2048; // デフォルト
static int g_adaptive_drain = -1;
if (__builtin_expect(g_adaptive_drain == -1, 0)) {
const char* env = getenv("HAKMEM_TINY_SLL_DRAIN_ADAPTIVE");
g_adaptive_drain = (env && *env && *env != '0') ? 1 : 0;
}
if (!g_adaptive_drain) {
return g_drain_interval_base; // 固定間隔モード
}
// Adaptive: Hot classes (0-3) drain more frequently
switch (class_idx) {
case 0: return 512; // 8B - 最頻使用
case 1: return 768; // 16B
case 2: return 768; // 32B
case 3: return 1024; // 64B
case 4: return 1536; // 128B
case 5: return 2048; // 256B
case 6: return 2048; // 512B
case 7: return 2048; // 2048B
default: return g_drain_interval_base;
}
}
```
**使い方**:
```bash
# Adaptive drain を有効化
export HAKMEM_TINY_SLL_DRAIN_ADAPTIVE=1
# または特定クラスの間隔を手動調整
export HAKMEM_TINY_SLL_DRAIN_INTERVAL=512
```
**影響**:
- **性能**: Hot classes の drain 頻度 ↑ → `meta->used` の正確性 ↑ → empty detection ↑
- **メモリ**: TLS SLL の最大サイズ ↓ → footprint ↓
---
### 2.2 同期マクロの導入
#### 設計: `TLS_SLL_PUSH_TRACKED` / `TLS_SLL_POP_TRACKED`
**目的**: TLS SLL 操作時に `meta->used` を追跡する(将来的な同期のための基盤)
**ファイル**: 新規作成 `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_tracked_box.h`
```c
#pragma once
#include "tls_sll_box.h"
#include "hakmem_tiny_superslab.h"
#include "superslab/superslab_inline.h"
// TLS_SLL_PUSH_TRACKED: Push to TLS SLL with optional meta->used tracking
//
// Args:
// cls: Size class index
// ptr: BASE pointer to push
// cap: Capacity limit
// ss: SuperSlab pointer (for meta->used tracking, can be NULL)
// slab_idx: Slab index (for meta->used tracking, can be -1)
//
// Returns: true on success, false on failure
//
// Behavior:
// - Always pushes to TLS SLL (normal path)
// - If ss != NULL && slab_idx >= 0: optionally track in meta->used (env-gated)
//
static inline bool TLS_SLL_PUSH_TRACKED(int cls, void* ptr, uint32_t cap,
SuperSlab* ss, int slab_idx) {
// Standard TLS SLL push
if (!tls_sll_push(cls, ptr, cap)) {
return false;
}
#if !HAKMEM_BUILD_RELEASE
// Optional: Track TLS SLL inventory in meta->used_tls_sll (future work)
// ENV: HAKMEM_TINY_TLS_SLL_TRACK_USED=1
static int g_track_used = -1;
if (__builtin_expect(g_track_used == -1, 0)) {
const char* env = getenv("HAKMEM_TINY_TLS_SLL_TRACK_USED");
g_track_used = (env && *env && *env != '0') ? 1 : 0;
}
if (g_track_used && ss && slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss)) {
// Future: Increment ss->slabs[slab_idx].used_tls_sll
// Note: Requires adding new field to TinySlabMeta
// For now, just log (diagnostic mode)
static _Atomic uint64_t g_track_log = 0;
uint64_t n = atomic_fetch_add(&g_track_log, 1);
if (n < 10) {
fprintf(stderr, "[TLS_SLL_TRACK] PUSH cls=%d ptr=%p ss=%p slab=%d\n",
cls, ptr, (void*)ss, slab_idx);
}
}
#else
(void)ss;
(void)slab_idx;
#endif
return true;
}
// TLS_SLL_POP_TRACKED: Pop from TLS SLL with optional meta->used tracking
static inline bool TLS_SLL_POP_TRACKED(int cls, void** out,
SuperSlab** ss_out, int* slab_idx_out) {
// Standard TLS SLL pop
if (!tls_sll_pop(cls, out)) {
return false;
}
// Resolve SuperSlab/slab for caller (optional metadata)
if (ss_out && slab_idx_out) {
SuperSlab* ss = hak_super_lookup(*out);
if (ss && ss->magic == SUPERSLAB_MAGIC) {
int sidx = slab_index_for(ss, *out);
if (sidx >= 0 && sidx < ss_slabs_capacity(ss)) {
*ss_out = ss;
*slab_idx_out = sidx;
#if !HAKMEM_BUILD_RELEASE
static int g_track_used = -1;
if (__builtin_expect(g_track_used == -1, 0)) {
const char* env = getenv("HAKMEM_TINY_TLS_SLL_TRACK_USED");
g_track_used = (env && *env && *env != '0') ? 1 : 0;
}
if (g_track_used) {
// Future: Decrement ss->slabs[sidx].used_tls_sll
static _Atomic uint64_t g_track_log = 0;
uint64_t n = atomic_fetch_add(&g_track_log, 1);
if (n < 10) {
fprintf(stderr, "[TLS_SLL_TRACK] POP cls=%d ptr=%p ss=%p slab=%d\n",
cls, *out, (void*)ss, sidx);
}
}
#endif
return true;
}
}
}
return true;
}
```
**使い方**:
```c
// Before (existing code)
tls_sll_push(class_idx, base, UINT32_MAX);
// After (with tracking)
TLS_SLL_PUSH_TRACKED(class_idx, base, UINT32_MAX, ss, slab_idx);
```
**段階的導入**:
1. Phase 1: マクロ導入、ログのみ(診断用)
2. Phase 2: `TinySlabMeta.used_tls_sll` フィールド追加
3. Phase 3: `meta->used` の代わりに `meta->used + meta->used_tls_sll` で empty 判定
---
### 2.3 Slab 再利用ガードの設計
#### 設計: `tiny_slab_can_reuse()`
**目的**: Slab を再利用する前に、TLS SLL に古いポインタが残っていないことを確認
**ファイル**: 新規作成 `/mnt/workdisk/public_share/hakmem/core/box/slab_reuse_guard_box.h`
```c
#pragma once
#include "hakmem_tiny_superslab.h"
#include "hakmem_tiny_config.h"
#include <stdint.h>
#include <stdbool.h>
// ============================================================================
// Slab Reuse Guard: Prevent reuse when TLS SLL contains stale pointers
// ============================================================================
//
// Problem:
// - Slab marked as EMPTY (meta->used == 0)
// - But TLS SLL still contains pointers from this slab
// - Slab gets reinitialized for different class
// - Old pointers in TLS SLL get allocated → double-free
//
// Solution:
// - Before reusing EMPTY slab, check if TLS SLL contains pointers from it
// - If yes, force drain TLS SLL first (or skip reuse)
//
// Design:
// - Development builds: strict check + force drain
// - Production builds: best-effort check (optional, env-gated)
// Check if TLS SLL contains pointers from given slab
// Returns: number of pointers found in TLS SLL from this slab
static inline uint32_t tiny_slab_count_tls_sll_refs(SuperSlab* ss, int slab_idx, int class_idx) {
if (!ss || slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) {
return 0;
}
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES];
// Calculate slab address range
uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx);
uintptr_t slab_start = (uintptr_t)slab_base;
uintptr_t slab_end = slab_start + SLAB_SIZE;
// Scan TLS SLL for this class
uint32_t found = 0;
void* cur = g_tls_sll[class_idx].head;
uint32_t scanned = 0;
const uint32_t limit = 1024; // Scan up to 1024 entries
while (cur && scanned < limit) {
uintptr_t addr = (uintptr_t)cur;
// Check if pointer is in this slab's range
if (addr >= slab_start && addr < slab_end) {
found++;
}
// Get next pointer
void* next = NULL;
PTR_NEXT_READ("reuse_guard_scan", class_idx, cur, 0, next);
cur = next;
scanned++;
}
return found;
}
// Guard: Check if slab can be safely reused
// Returns: true if safe to reuse, false if TLS SLL drain needed
static inline bool tiny_slab_can_reuse(SuperSlab* ss, int slab_idx, int class_idx) {
#if !HAKMEM_BUILD_RELEASE
// Development: Always check for safety
uint32_t refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
if (refs > 0) {
fprintf(stderr,
"[SLAB_REUSE_GUARD] BLOCKED: cls=%d ss=%p slab=%d has %u refs in TLS SLL\n",
class_idx, (void*)ss, slab_idx, refs);
// Force drain TLS SLL to clear stale pointers
extern uint32_t tiny_tls_sll_drain(int class_idx, uint32_t batch_size);
uint32_t drained = tiny_tls_sll_drain(class_idx, 0); // Drain all
fprintf(stderr,
"[SLAB_REUSE_GUARD] Forced drain: cls=%d drained=%u blocks\n",
class_idx, drained);
// Re-check after drain
refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
if (refs > 0) {
fprintf(stderr,
"[SLAB_REUSE_GUARD] CRITICAL: Still %u refs after drain! (memory corruption?)\n",
refs);
return false; // Unsafe to reuse
}
}
return true; // Safe to reuse
#else
// Production: Optional check (env-gated for minimal overhead)
static int g_reuse_guard = -1;
if (__builtin_expect(g_reuse_guard == -1, 0)) {
const char* env = getenv("HAKMEM_TINY_SLAB_REUSE_GUARD");
g_reuse_guard = (env && *env && *env != '0') ? 1 : 0;
}
if (!g_reuse_guard) {
return true; // Guard disabled in production
}
// Same logic as development build
uint32_t refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
if (refs > 0) {
// Production: Just skip reuse (don't force drain to avoid latency spike)
return false;
}
return true;
#endif
}
```
**統合場所**: `hakmem_shared_pool.c``shared_pool_acquire_slab()`
```c
// shared_pool_acquire_slab() 内 (Stage 1: EMPTY slot reuse の前)
// Before reusing EMPTY slot, check TLS SLL safety
if (!tiny_slab_can_reuse(ss, slot_idx, class_idx)) {
// Skip this EMPTY slot (TLS SLL drain needed first)
fprintf(stderr, "[ACQUIRE_SLAB] Skipped EMPTY slot due to TLS SLL refs\n");
continue; // Try next slot
}
```
**影響**:
- **開発ビルド**: 常に有効、TLS SLL の自動 drain 付き
- **本番ビルド**: ENV で有効化可能、drain なし(スキップのみ)
- **性能**: 開発ビルドで O(TLS_SLL_count) のスキャン、本番では無視できる
---
## Part 3: アーキテクチャ改善
### 3.1 ポインタライフサイクル管理
#### 状態遷移図(改善版)
```
┌─────────────────────────────────────────────────────────────┐
│ Pointer Lifecycle States │
└─────────────────────────────────────────────────────────────┘
[UNCARVED] (Slab 初期化直後、meta->used < meta->capacity)
|
| superslab_alloc_from_slab() - Linear carve
v
[CARVED] (meta->used++, user に返す直前)
|
| return block to user
v
[ALLOCATED] (ユーザーコードが使用中)
|
| user calls free(ptr)
v
+---> [FAST FREE PATH 99%] ----+ +---> [SLOW FREE PATH 1%]
| | |
| hak_tiny_free_fast_v2() | | hak_tiny_free()
| - Header から class_idx 読取 | | - SuperSlab lookup
| - tls_sll_push(ptr) | | - Ownership 確認
| (meta->used 不変!) | |
| | |
v | v
[IN_TLS_SLL] | [FREELIST]
| - TLS SLL に格納 | | - tiny_free_local_box()
| - meta->used は高いまま | | - freelist に push
| - periodic drain を待つ | | - meta->used-- ★
| | |
| (2048 frees 後) | |
v | |
[DRAINING] | |
| tiny_tls_sll_drain() | |
| - TLS SLL から pop +<----+
| - tiny_free_local_box() 呼出
| - meta->used-- ★
v
[FREELIST] (meta->freelist に連結、meta->used 正確)
|
| (meta->used == 0 なら)
v
[EMPTY] (ss_mark_slab_empty())
|
| shared_pool_acquire_slab() - Stage 1
| → tiny_slab_can_reuse() ガード ★
v
[REUSABLE] (再初期化可能)
|
| superslab_init_slab()
v
[UNCARVED] (ループ)
```
**重要な Invariants**:
1. **Meta->used の正確性**
```c
// Invariant 1: meta->used reflects allocated + in_freelist
// (TLS SLL 内のポインタは含まれない!)
meta->used == allocated_count + freelist_count // 不正確
// Correct invariant (with TLS SLL):
meta->used == allocated_count + freelist_count + tls_sll_count
```
2. **Empty 判定の条件**
```c
// Current (WRONG):
is_empty = (meta->used == 0); // TLS SLL に残っている可能性
// Fixed (with guard):
is_empty = (meta->used == 0) && !tiny_slab_has_tls_sll_refs(ss, slab_idx, cls);
```
3. **TLS SLL のクリーン状態**
```c
// Invariant 3: TLS SLL must be drained before slab reuse
// Enforced by: tiny_slab_can_reuse() guard
```
### 3.2 コード構造の改善
#### 責任の分離
| モジュール | 責任 | 主要 API |
|-----------|------|---------|
| **TLS SLL Box** | TLS 単方向リスト管理 | `tls_sll_push/pop/splice` |
| **TLS SLL Drain Box** | Periodic drain、meta->used 同期 | `tiny_tls_sll_drain/try_drain` |
| **Slab Meta Box** | Slab メタデータ管理 | `ss_slab_meta_*` |
| **Slab Reuse Guard Box** | 再利用安全性チェック | `tiny_slab_can_reuse` |
| **Free Local Box** | Freelist 管理、meta->used 更新 | `tiny_free_local_box` |
| **Shared Pool** | SuperSlab 割り当て、slot 管理 | `shared_pool_acquire/release_slab` |
#### ファイル構成の提案
```
core/
├── box/
│ ├── tls_sll_box.h (既存) Push/Pop/Splice
│ ├── tls_sll_drain_box.h (既存) Periodic drain
│ ├── tls_sll_tracked_box.h (新規) Tracked push/pop
│ ├── slab_reuse_guard_box.h (新規) Reuse safety guard
│ ├── free_local_box.{h,c} (既存) Freelist + meta->used--
│ ├── ss_slab_meta_box.h (既存) Meta accessors
│ └── ss_hot_cold_box.h (既存) Hot/Cold/Empty marking
├── hakmem_shared_pool.{h,c} (既存) Shared pool
├── tiny_superslab_alloc.inc.h (既存) Alloc paths
└── tiny_free_fast_v2.inc.h (既存) Fast free path
```
### 3.3 デバッグ・診断機能の強化
#### 新規追加すべき診断機能
##### A. TLS SLL 在庫トラッキング
**ENV**: `HAKMEM_TINY_TLS_SLL_INVENTORY=1`
```c
// Per-class TLS SLL inventory statistics (TLS)
static __thread struct {
uint64_t total_push; // 累計 push 回数
uint64_t total_pop; // 累計 pop 回数
uint64_t total_drained; // 累計 drain 回数
uint32_t peak_count; // 最大 count
uint32_t current_count; // 現在の count
} g_tls_sll_stats[TINY_NUM_CLASSES];
// Destructor で出力
static void tls_sll_inventory_report(void) __attribute__((destructor));
```
##### B. Slab 再利用ヒストリー
**ENV**: `HAKMEM_TINY_SLAB_REUSE_HISTORY=1`
```c
// Per-slab reuse history (circular buffer)
#define REUSE_HISTORY_SIZE 16
typedef struct {
uint64_t timestamp_ns; // 再利用時刻
uint8_t prev_class_idx; // 前のクラス
uint8_t new_class_idx; // 新しいクラス
uint16_t tls_sll_refs; // 再利用時の TLS SLL 参照数
} SlabReuseEvent;
// Per SuperSlab
SlabReuseEvent reuse_history[SLABS_PER_SUPERSLAB_MAX][REUSE_HISTORY_SIZE];
uint32_t reuse_index[SLABS_PER_SUPERSLAB_MAX];
```
##### C. Meta->used 整合性チェック
**ENV**: `HAKMEM_TINY_META_USED_VERIFY=1`
```c
// Periodic verification: count actual allocations vs meta->used
// (Expensive, debug only)
static void verify_meta_used_consistency(SuperSlab* ss, int slab_idx) {
TinySlabMeta* meta = &ss->slabs[slab_idx];
// Count freelist entries
uint32_t freelist_count = 0;
void* cur = meta->freelist;
while (cur && freelist_count < meta->capacity) {
freelist_count++;
cur = tiny_next_read(meta->class_idx, cur);
}
// Expected: meta->used + freelist_count <= meta->capacity
if (meta->used + freelist_count > meta->capacity) {
fprintf(stderr, "[META_USED_VERIFY] CORRUPTION: used=%u freelist=%u cap=%u\n",
meta->used, freelist_count, meta->capacity);
abort();
}
}
```
#### 性能への影響を最小化
**戦略**:
1. **本番ビルドでは完全無効化**: `#if !HAKMEM_BUILD_RELEASE` でガード
2. **ENV ゲート**: デフォルト OFF、必要時のみ有効化
3. **サンプリング**: 全操作ではなく N 回に 1 回だけチェック
4. **TLS カウンタ**: Atomic 操作を避ける
### 3.4 ドキュメント化
#### MEMORY_LIFECYCLE.md の内容提案
```markdown
# メモリライフサイクル設計書
## 概要
HAKMEM の Tiny Pool におけるポインタのライフサイクルと状態遷移を定義。
## 状態定義
### UNCARVED
- **定義**: Slab 初期化直後、まだ carve されていない領域
- **条件**: `meta->used < meta->capacity`
- **次状態**: CARVED (alloc 時)
### CARVED
- **定義**: Slab から carve され、ユーザーに返す直前
- **操作**: `meta->used++`
- **次状態**: ALLOCATED
### ALLOCATED
- **定義**: ユーザーコードが使用中
- **Invariant**: ポインタはユーザー空間にあり、内部リストには未登録
- **次状態**: IN_TLS_SLL (fast free) or FREELIST (slow free)
### IN_TLS_SLL
- **定義**: TLS SLL に push された、drain 待ち
- **Invariant**:
- `g_tls_sll[class_idx]` のリスト内に存在
- `meta->used` は変更なし(高いまま)
- Periodic drain まで freelist に戻らない
- **次状態**: DRAINING (periodic drain)
### DRAINING
- **定義**: TLS SLL から freelist に移動中
- **操作**:
1. `tls_sll_pop()`
2. `tiny_free_local_box()``meta->used--`
3. Freelist に push
- **次状態**: FREELIST
### FREELIST
- **定義**: Slab の freelist に連結
- **Invariant**:
- `meta->freelist` のリスト内に存在
- `meta->used` は正確decrement 済み)
- **次状態**: ALLOCATED (再利用) or EMPTY (すべて free)
### EMPTY
- **定義**: Slab 内のすべてのブロックが free
- **条件**: `meta->used == 0`
- **マーク**: `ss_mark_slab_empty(ss, slab_idx)`
- **次状態**: REUSABLE (再利用準備)
### REUSABLE
- **定義**: 別のクラスで再初期化可能
- **ガード**: `tiny_slab_can_reuse()` でチェック
- **操作**: `superslab_init_slab()`
- **次状態**: UNCARVED
## 状態遷移の Invariants
### Invariant 1: Meta->used の意味
```c
// WRONG (current):
meta->used == allocated_count + freelist_count
// CORRECT (with TLS SLL):
meta->used == allocated_count + freelist_count + tls_sll_count
```
**問題**: TLS SLL 内のポインタは `meta->used` に反映されない。
### Invariant 2: Empty 判定
```c
// WRONG:
is_empty = (meta->used == 0);
// CORRECT:
is_empty = (meta->used == 0) &&
!tiny_slab_has_tls_sll_refs(ss, slab_idx, class_idx);
```
### Invariant 3: Slab 再利用前の TLS SLL クリーン
```c
// Before reuse:
tiny_slab_can_reuse(ss, slab_idx, class_idx) == true
⇒ TLS SLL に当該 slab のポインタが存在しない
```
## デバッグツール
- `HAKMEM_TINY_TLS_SLL_DIAG=1`: TLS SLL 診断
- `HAKMEM_TINY_SLAB_REUSE_GUARD=1`: 再利用ガード(本番)
- `HAKMEM_TINY_META_USED_VERIFY=1`: Meta->used 検証(開発)
## 参考文献
- `/mnt/workdisk/public_share/hakmem/docs/analysis/LARSON_DOUBLE_FREE_INVESTIGATION.md`
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h`
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
```
### 3.5 テスト戦略
#### 単体テスト
**ファイル**: 新規作成 `tests/test_tls_sll_lifecycle.c`
```c
// Test 1: TLS SLL push/pop/drain cycle
void test_tls_sll_basic_cycle(void) {
int class_idx = 1; // 16B
void* ptr1 = allocate_test_block(class_idx);
void* ptr2 = allocate_test_block(class_idx);
// Push to TLS SLL
assert(tls_sll_push(class_idx, ptr1, 1000));
assert(g_tls_sll[class_idx].count == 1);
// Pop from TLS SLL
void* out;
assert(tls_sll_pop(class_idx, &out));
assert(out == ptr1);
assert(g_tls_sll[class_idx].count == 0);
// Drain empty list (no-op)
uint32_t drained = tiny_tls_sll_drain(class_idx, 0);
assert(drained == 0);
}
// Test 2: Slab reuse guard
void test_slab_reuse_guard(void) {
int class_idx = 2; // 32B
SuperSlab* ss = get_test_superslab();
int slab_idx = 0;
// Allocate and free blocks (push to TLS SLL)
for (int i = 0; i < 10; i++) {
void* ptr = allocate_from_slab(ss, slab_idx);
tls_sll_push(class_idx, ptr, 1000);
}
// Manually mark slab as empty (simulate meta->used == 0)
ss->slabs[slab_idx].used = 0;
ss_mark_slab_empty(ss, slab_idx);
// Reuse guard should detect TLS SLL refs
assert(!tiny_slab_can_reuse(ss, slab_idx, class_idx));
// Drain TLS SLL
tiny_tls_sll_drain(class_idx, 0);
// Now reuse should be safe
assert(tiny_slab_can_reuse(ss, slab_idx, class_idx));
}
// Test 3: Meta->used consistency after drain
void test_meta_used_consistency(void) {
// TODO: Implement
}
```
#### 統合テスト
**ベンチマーク**: Larson + 拡張
```bash
# 現在の Larson (MT stress test)
./mimalloc-bench/out/bench/larson 10 5 200000
# 追加テスト: Slab 再利用を強制
export HAKMEM_TINY_SLL_DRAIN_INTERVAL=100 # 頻繁に drain
export HAKMEM_TINY_SLAB_REUSE_GUARD=1 # ガード有効
./mimalloc-bench/out/bench/larson 10 5 500000
# 診断モード
export HAKMEM_TINY_SLL_DIAG=1
export HAKMEM_TINY_SLAB_REUSE_HISTORY=1
./mimalloc-bench/out/bench/larson 10 5 100000 2>larson_diag.log
```
#### Fuzzing
**ツール**: AFL++ or libFuzzer
```c
// Fuzzing harness for TLS SLL operations
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size < 4) return 0;
int class_idx = data[0] % TINY_NUM_CLASSES;
uint8_t op = data[1];
switch (op % 4) {
case 0: { // Push
void* ptr = allocate_test_block(class_idx);
tls_sll_push(class_idx, ptr, 1000);
break;
}
case 1: { // Pop
void* out;
tls_sll_pop(class_idx, &out);
break;
}
case 2: { // Drain
tiny_tls_sll_drain(class_idx, data[2]);
break;
}
case 3: { // Slab reuse
// Simulate slab reuse
break;
}
}
return 0;
}
```
---
## Part 4: リファクタリング計画
### 段階的な改善ステップ
#### Phase 1: 即座の修正1-2 日)
**目標**: Larson double-free バグを修正
| ステップ | ファイル | 変更内容 | 工数 |
|---------|---------|---------|------|
| 1.1 | `tiny_superslab_alloc.inc.h` | Slab refill 時の TLS SLL drain | 小 |
| 1.2 | `tls_sll_drain_box.h` | Adaptive drain interval | 小 |
| 1.3 | テスト | Larson 100 runs → 0% crash | 中 |
**期待される結果**:
- ✅ Larson ベンチマークで double-free が発生しない
- ✅ 既存の性能を維持(変更は診断コードのみ)
#### Phase 2: ガード機構の導入3-5 日)
**目標**: 再発防止のための予防的チェック
| ステップ | ファイル | 変更内容 | 工数 |
|---------|---------|---------|------|
| 2.1 | `box/slab_reuse_guard_box.h` | Reuse guard 実装 | 中 |
| 2.2 | `hakmem_shared_pool.c` | Guard 統合 | 小 |
| 2.3 | テスト | 単体テスト作成 | 中 |
**期待される結果**:
- ✅ Slab 再利用時に TLS SLL の残留を検出
- ✅ 開発ビルドで自動 drain、本番ビルドで ENV ゲート
#### Phase 3: 診断機能の強化5-7 日)
**目標**: デバッグ効率の向上
| ステップ | ファイル | 変更内容 | 工数 |
|---------|---------|---------|------|
| 3.1 | `box/tls_sll_tracked_box.h` | Tracked push/pop マクロ | 中 |
| 3.2 | 診断統計 | TLS SLL inventory tracking | 小 |
| 3.3 | 診断統計 | Slab reuse history | 中 |
| 3.4 | ドキュメント | `MEMORY_LIFECYCLE.md` 作成 | 中 |
**期待される結果**:
- ✅ TLS SLL の挙動を可視化
- ✅ Slab 再利用のヒストリーを追跡
- ✅ 将来的な meta->used 同期の準備
#### Phase 4: アーキテクチャ改善14-21 日)
**目標**: 根本的な解決meta->used の正確性)
| ステップ | ファイル | 変更内容 | 工数 |
|---------|---------|---------|------|
| 4.1 | `superslab_types.h` | `TinySlabMeta.used_tls_sll` フィールド追加 | 小 |
| 4.2 | `box/tls_sll_tracked_box.h` | `used_tls_sll` の increment/decrement | 中 |
| 4.3 | `box/ss_hot_cold_box.h` | Empty 判定を `used + used_tls_sll` に変更 | 小 |
| 4.4 | 全体統合 | Tracked マクロを全箇所に適用 | 大 |
| 4.5 | テスト | 統合テスト、Larson 長時間実行 | 中 |
**期待される結果**:
-`meta->used` が常に正確
- ✅ TLS SLL 内のポインタも追跡
- ✅ Empty 判定が正しく動作
### 優先順位の提案
| フェーズ | 緊急度 | 重要度 | 推奨順序 |
|---------|-------|-------|---------|
| Phase 1 | 🔴 高 | 🔴 高 | 1位即座に実施 |
| Phase 2 | 🟡 中 | 🔴 高 | 2位Phase 1 の後) |
| Phase 3 | 🟢 低 | 🟡 中 | 3位並行可能 |
| Phase 4 | 🟢 低 | 🔴 高 | 4位長期計画 |
---
## Part 5: 設計ドキュメント草案
### ポインタライフサイクル図
```
┌───────────────────────────────────────────────────────────────────┐
│ HAKMEM Tiny Pool - Pointer Lifecycle │
└───────────────────────────────────────────────────────────────────┘
States:
UNCARVED → CARVED → ALLOCATED → IN_TLS_SLL → DRAINING → FREELIST → EMPTY → REUSABLE
Critical Paths:
1. Alloc (Fast): UNCARVED → CARVED → ALLOCATED
2. Free (Fast): ALLOCATED → IN_TLS_SLL
3. Free (Slow): ALLOCATED → FREELIST
4. Drain: IN_TLS_SLL → DRAINING → FREELIST
5. Reuse: EMPTY → REUSABLE → UNCARVED
Invariants:
I1: meta->used = allocated + freelist + tls_sll (total blocks not in UNCARVED)
I2: is_empty ⇔ (meta->used == 0) ∧ (tls_sll_refs == 0)
I3: reusable ⇔ is_empty ∧ tiny_slab_can_reuse() == true
Guards:
G1: tiny_slab_can_reuse() - Prevents reuse when TLS SLL has refs
G2: tls_sll_push() duplicate check - Prevents double-free in TLS SLL
G3: tiny_free_local_box() - Only path that decrements meta->used
Synchronization Points:
S1: Periodic drain (every N frees) - Syncs TLS SLL → freelist
S2: Slab refill - Forces drain if TLS SLL has refs
S3: Slab reuse - Blocks if TLS SLL has refs
```
### 不変条件リスト
#### I1: Meta->used の正確性
```c
// Definition:
// meta->used counts all blocks that have been carved but not returned to freelist
// INCLUDING blocks in TLS SLL (not yet drained)
meta->used == SUM(state == ALLOCATED || state == IN_TLS_SLL || state == FREELIST)
// Corollary:
// Empty detection requires checking BOTH meta->used AND TLS SLL
is_empty(slab) ⟺ (meta->used == 0) ∧ (tls_sll_count(slab) == 0)
```
#### I2: TLS SLL の一貫性
```c
// Definition:
// TLS SLL contains only pointers that:
// 1. Were allocated from this allocator
// 2. Have valid headers (magic + class_idx)
// 3. Are still in ALLOCATED or IN_TLS_SLL state
∀ptr ∈ TLS_SLL[class]:
- ptr is BASE pointer (user - 1)
- header[ptr].magic == HEADER_MAGIC
- header[ptr].class_idx == class
- state[ptr] == IN_TLS_SLL
```
#### I3: Slab 再利用の安全性
```c
// Definition:
// Slab can be reused only if:
// 1. meta->used == 0 (all blocks returned)
// 2. No TLS SLL contains pointers from this slab
// 3. Remote queues are drained
tiny_slab_can_reuse(ss, slab_idx, class) ⟺
(meta->used == 0) ∧
(tls_sll_count_refs(ss, slab_idx, class) == 0) ∧
(remote_count[slab_idx] == 0)
```
#### I4: Drain の冪等性
```c
// Definition:
// Draining an empty TLS SLL is a no-op
TLS_SLL[class].count == 0 ⟹ tiny_tls_sll_drain(class, N) == 0
```
### API 契約の明文化
#### `tls_sll_push(class_idx, ptr, capacity)`
**事前条件**:
- `0 <= class_idx < TINY_NUM_CLASSES`
- `ptr` は BASE ポインタuser - 1
- `ptr` は現在 TLS SLL に含まれていない(重複不可)
- `capacity > 0`
**事後条件**:
- 成功時: `TLS_SLL[class].count++`, `ptr` がリストの先頭
- 失敗時: 状態変更なし
- **重要**: `meta->used` は変更されない
**副作用**:
- Header 書き戻し(クラス 1-6 のみ)
- 重複チェックdebug ビルドのみ、先頭 256 要素)
---
#### `tls_sll_pop(class_idx, *out)`
**事前条件**:
- `0 <= class_idx < TINY_NUM_CLASSES`
- `out != NULL`
**事後条件**:
- 成功時: `TLS_SLL[class].count--`, `*out` に BASE ポインタ
- 失敗時: `return false`, `*out` は未定義
- **重要**: `meta->used` は変更されない
**副作用**:
- Next ポインタのクリア(リスト切り離し)
- Header 検証debug ビルド)
---
#### `tiny_tls_sll_drain(class_idx, batch_size)`
**事前条件**:
- `0 <= class_idx < TINY_NUM_CLASSES`
- `batch_size == 0` → すべて drain
**事後条件**:
- `N = min(batch_size, TLS_SLL[class].count)` 個の要素を drain
- 各要素に対して `tiny_free_local_box()` 呼び出し
- **重要**: `meta->used -= N`freelist 経由)
**副作用**:
- Freelist への追加
- `meta->used` の decrement
- Empty マーク(`meta->used == 0` の場合)
---
#### `tiny_slab_can_reuse(ss, slab_idx, class_idx)`
**事前条件**:
- `ss` は有効な SuperSlab
- `0 <= slab_idx < ss_slabs_capacity(ss)`
- `meta->used == 0`caller が確認済み)
**事後条件**:
- 成功時: `return true`, TLS SLL に refs なし
- 失敗時: `return false`, TLS SLL に refs ありdrain 必要)
**副作用** (debug ビルドのみ):
- TLS SLL の自動 drainrefs が見つかった場合)
- ログ出力
---
## 付録 A: 主要ファイルのサマリー
### `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h`
**役割**: TLS SLL のコア実装push/pop/splice
**主要関数**:
- `tls_sll_push_impl()`: 要素を TLS SLL に追加(重複チェック付き)
- `tls_sll_pop_impl()`: 要素を TLS SLL から取り出し
- `tls_sll_splice()`: チェーンを一括で TLS SLL に統合
**重要な設計**:
- すべて BASE ポインタで動作
- Header の読み書き(クラス 1-6
- 重複検出debug ビルド、先頭 256 要素)
---
### `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
**役割**: Periodic drainOption Bの実装
**主要関数**:
- `tiny_tls_sll_drain()`: TLS SLL から freelist に移動
- `tiny_tls_sll_try_drain()`: Counter ベースの自動 drain
**設計**:
- デフォルト間隔: 2048 frees
- ENV: `HAKMEM_TINY_SLL_DRAIN_INTERVAL` で調整可能
- **重要**: `tiny_free_local_box()` 経由で `meta->used--`
---
### `/mnt/workdisk/public_share/hakmem/core/box/free_local_box.c`
**役割**: Freelist への追加 + `meta->used` の decrement
**主要関数**:
- `tiny_free_local_box()`: **唯一の `meta->used--` 実行箇所**
**フロー**:
1. BASE ポインタ計算user - 1
2. Freelist に push
3. **`meta->used--`** ← ここが重要
4. `meta->used == 0` なら `ss_mark_slab_empty()`
---
### `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h`
**役割**: Fast free pathHeader ベース)
**主要関数**:
- `hak_tiny_free_fast_v2()`: Header から class 読み取り → TLS SLL に push
**フロー**:
1. Header から `class_idx` 読み取り2-3 cycles
2. `tls_sll_push(class_idx, base, UINT32_MAX)`
3. Periodic drain トリガー2048 frees ごと)
**重要**: `meta->used` は変更しないdrain 時に初めて更新)
---
### `/mnt/workdisk/public_share/hakmem/core/hakmem_shared_pool.c`
**役割**: SuperSlab の割り当てと slot 管理
**主要関数**:
- `shared_pool_acquire_slab()`: 3-stage allocation
- Stage 1: EMPTY slots最優先
- Stage 2: UNUSED slots
- Stage 3: 新規 SuperSlab
**問題箇所**: Stage 1 で EMPTY slot を再利用する時、TLS SLL のチェックがない
---
## 付録 B: 環境変数リファレンス
| 環境変数 | デフォルト | 説明 | ファイル |
|---------|-----------|------|---------|
| `HAKMEM_TINY_SLL_DIAG` | 0 | TLS SLL 詳細診断 | tls_sll_box.h:118 |
| `HAKMEM_TINY_SLL_DRAIN_ENABLE` | 1 | Periodic drain 有効化 | tls_sll_drain_box.h:40 |
| `HAKMEM_TINY_SLL_DRAIN_INTERVAL` | 2048 | Drain 間隔frees 単位) | tls_sll_drain_box.h:54 |
| `HAKMEM_TINY_SLL_DRAIN_DEBUG` | 0 | Drain 詳細ログ | tls_sll_drain_box.h:121 |
| `HAKMEM_TINY_SLL_DRAIN_ADAPTIVE` | 0 | Class ごとの adaptive 間隔 | (新規提案) |
| `HAKMEM_TINY_SLL_SAFEHEADER` | 0 | Header 上書き防止 | tls_sll_box.h:336 |
| `HAKMEM_TINY_LARSON_FIX` | 0 | Cross-thread free 検出 | tiny_free_fast_v2.inc.h:189 |
| `HAKMEM_TINY_SLAB_REUSE_GUARD` | 0 (本番) | Slab 再利用ガード | (新規提案) |
| `HAKMEM_TINY_TLS_SLL_TRACK_USED` | 0 | TLS SLL inventory 追跡 | (新規提案) |
| `HAKMEM_TINY_META_USED_VERIFY` | 0 | Meta->used 整合性検証 | (新規提案) |
---
## まとめ
### 即座に実施すべき修正
1. **Slab refill 時の TLS SLL drain** (`tiny_superslab_alloc.inc.h`)
- 工数: 小1-2 時間)
- 影響: Larson double-free の根本修正
2. **Adaptive drain interval** (`tls_sll_drain_box.h`)
- 工数: 小2-3 時間)
- 影響: Hot classes の drain 頻度 ↑ → `meta->used` 正確性 ↑
### 中期的な改善
3. **Slab reuse guard** (`box/slab_reuse_guard_box.h`)
- 工数: 中1-2 日)
- 影響: 再発防止の予防的チェック
4. **診断機能の強化**
- 工数: 中3-5 日)
- 影響: デバッグ効率の向上
### 長期的なアーキテクチャ改善
5. **Meta->used の正確な同期** (`TinySlabMeta.used_tls_sll`)
- 工数: 大14-21 日)
- 影響: 根本的な解決、将来的な問題の予防
---
**END OF REPORT**