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>
1350 lines
43 KiB
Markdown
1350 lines
43 KiB
Markdown
# 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 の自動 drain(refs が見つかった場合)
|
||
- ログ出力
|
||
|
||
---
|
||
|
||
## 付録 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 drain(Option 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 path(Header ベース)
|
||
|
||
**主要関数**:
|
||
- `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**
|