Files
hakmem/docs/design/BOX_THEORY_VERIFICATION_REPORT.md

523 lines
19 KiB
Markdown
Raw Normal View History

Wrap debug fprintf in !HAKMEM_BUILD_RELEASE guards (Release build optimization) ## Changes ### 1. core/page_arena.c - Removed init failure message (lines 25-27) - error is handled by returning early - All other fprintf statements already wrapped in existing #if !HAKMEM_BUILD_RELEASE blocks ### 2. core/hakmem.c - Wrapped SIGSEGV handler init message (line 72) - CRITICAL: Kept SIGSEGV/SIGBUS/SIGABRT error messages (lines 62-64) - production needs crash logs ### 3. core/hakmem_shared_pool.c - Wrapped all debug fprintf statements in #if !HAKMEM_BUILD_RELEASE: - Node pool exhaustion warning (line 252) - SP_META_CAPACITY_ERROR warning (line 421) - SP_FIX_GEOMETRY debug logging (line 745) - SP_ACQUIRE_STAGE0.5_EMPTY debug logging (line 865) - SP_ACQUIRE_STAGE0_L0 debug logging (line 803) - SP_ACQUIRE_STAGE1_LOCKFREE debug logging (line 922) - SP_ACQUIRE_STAGE2_LOCKFREE debug logging (line 996) - SP_ACQUIRE_STAGE3 debug logging (line 1116) - SP_SLOT_RELEASE debug logging (line 1245) - SP_SLOT_FREELIST_LOCKFREE debug logging (line 1305) - SP_SLOT_COMPLETELY_EMPTY debug logging (line 1316) - Fixed lock_stats_init() for release builds (lines 60-65) - ensure g_lock_stats_enabled is initialized ## Performance Validation Before: 51M ops/s (with debug fprintf overhead) After: 49.1M ops/s (consistent performance, fprintf removed from hot paths) ## Build & Test ```bash ./build.sh larson_hakmem ./out/release/larson_hakmem 1 5 1 1000 100 10000 42 # Result: 49.1M ops/s ``` Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 13:14:18 +09:00
# Box Theory 残り境界の徹底検証レポート
## 調査概要
HAKMEM tiny allocator の Box Theory箱理論における 3つの残り境界Box 3, 2, 4の詳細検証結果。
検証対象ファイル:
- core/hakmem_tiny_free.inc (メイン free ロジック)
- core/slab_handle.h (所有権管理)
- core/tiny_publish.c publish 実装)
- core/tiny_mailbox.c mailbox 実装)
- core/tiny_remote.c remote queue 操作)
- core/hakmem_tiny_superslab.h owner/drain 実装)
- core/hakmem_tiny.c publish/adopt 実装)
---
## Box 3: Same-thread Freelist Push 検証
### 不変条件
**freelist への push は `owner_tid == my_tid` の時のみ**
### 検証結果
#### ✅ 問題なし: slab_handle.h の slab_freelist_push()
```c
// core/slab_handle.h:205-236
static inline int slab_freelist_push(SlabHandle* h, void* ptr) {
if (!h || !h->valid) {
return 0; // Box: No ownership → FAIL
}
// ...
// Ownership guaranteed by valid==1 → safe to modify freelist
*(void**)ptr = h->meta->freelist;
h->meta->freelist = ptr;
// ...
return 1;
}
```
✓ 所有権チェックvalid==1を確認後のみ freelist 操作
✓ 直接 freelist push の唯一の安全な入口
#### ✅ 問題なし: hakmem_tiny_free.inc の same-thread freelist push
```c
// core/hakmem_tiny_free.inc:1044-1076
if (!g_tiny_force_remote && meta->owner_tid != 0 && meta->owner_tid == my_tid) {
// Fast path: Direct freelist push (same-thread)
// ...
if (!tiny_remote_guard_allow_local_push(ss, slab_idx, meta, ptr, "local_free", my_tid)) {
// Fall back to remote if guard fails
int transitioned = ss_remote_push(ss, slab_idx, ptr);
// ...
return;
}
void* prev = meta->freelist;
*(void**)ptr = prev;
meta->freelist = ptr; // ← Safe freelist push
// ...
}
```
✓ owner_tid == my_tid の厳密なチェック
✓ guard check で追加の安全性確保
✓ owner_tid != my_tid の場合は確実に remote_push へ
#### ✅ 問題なし: publish 時の owner_tid リセット
```c
// core/hakmem_tiny.c:639-670 (ss_partial_publish)
for (int s = 0; s < cap_pub; s++) {
uint32_t prev = __atomic_exchange_n(&ss->slabs[s].owner_tid, 0u, __ATOMIC_RELEASE);
// ...記録のみ...
}
```
✓ publish 時に明示的に owner_tid=0 をセット
✓ ATOMIC_RELEASE で memory barrier 確保
**Box 3 評価: ✅ PASS - 境界は堅牢。直接 freelist push は所有権ガード完全。**
---
## Box 2: Remote Push の重複dup_push検証
### 不変条件
**同じノードを remote queue に二重 push しない**
### 検証結果
#### ✅ 問題なし: tiny_remote_queue_contains_guard()
```c
// core/hakmem_tiny_free.inc:10-30
static inline int tiny_remote_queue_contains_guard(SuperSlab* ss, int slab_idx, void* target) {
if (!ss || slab_idx < 0) return 0;
uintptr_t cur = atomic_load_explicit(&ss->remote_heads[slab_idx], memory_order_acquire);
int limit = 8192;
while (cur && limit-- > 0) {
if ((void*)cur == target) {
return 1; // Found duplicate
}
uintptr_t next;
if (__builtin_expect(g_remote_side_enable, 0)) {
next = tiny_remote_side_get(ss, slab_idx, (void*)cur);
} else {
next = atomic_load_explicit((_Atomic uintptr_t*)cur, memory_order_relaxed);
}
cur = next;
}
if (limit <= 0) {
return 1; // fail-safe: treat unbounded traversal as duplicate
}
return 0;
}
```
✓ 8192 ノード上限でループ安全化
✓ Fail-safe: 上限に達したら dup として扱うconservative
✓ remote_side 両対応
#### ✅ 問題なし: free 時の dup_remote チェック
```c
// core/hakmem_tiny_free.inc:1183-1197
int dup_remote = tiny_remote_queue_contains_guard(ss, slab_idx, ptr);
if (!dup_remote && __builtin_expect(g_remote_side_enable, 0)) {
dup_remote = (head_word == TINY_REMOTE_SENTINEL) ||
tiny_remote_side_contains(ss, slab_idx, ptr);
}
// ...
if (dup_remote) {
uintptr_t aux = tiny_remote_pack_diag(0xA214u, ss_base, ss_size, (uintptr_t)ptr);
tiny_remote_watch_mark(ptr, "dup_prevent", my_tid);
tiny_remote_watch_note("dup_prevent", ss, slab_idx, ptr, 0xA214u, my_tid, 0);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, ptr, aux);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return; // ← Prevent double-push
}
```
✓ 二重チェックqueue walk + side table
✓ A214 コードdup_preventで検出を記録
✓ Fail-Fast: 検出後は即座に returnpush しない)
#### ✅ 問題なし: ss_remote_push() の CAS ループ
```c
// core/hakmem_tiny_superslab.h:282-376
_Atomic(uintptr_t)* head = &ss->remote_heads[slab_idx];
uintptr_t old;
do {
old = atomic_load_explicit(head, memory_order_acquire);
if (!g_remote_side_enable) {
*(void**)ptr = (void*)old; // legacy embedding
}
} while (!atomic_compare_exchange_weak_explicit(head, &old, (uintptr_t)ptr,
memory_order_release,
memory_order_relaxed));
```
✓ CAS ループで atomic な single-pop-then-push
✓ ptr は new head になるのみ(二重化不可)
#### ✅ 問題なし: tiny_remote_side_set() で remote_side への重複登録防止
```c
// core/tiny_remote.c:529-575
uint32_t i = hmix(k) & (REM_SIDE_SIZE - 1);
for (uint32_t n=0; n<REM_SIDE_SIZE; n++, i=(i+1)&(REM_SIDE_SIZE-1)) {
uintptr_t expect = 0;
if (atomic_compare_exchange_weak_explicit(&g_rem_side[i].key, &expect, k,
memory_order_acq_rel,
memory_order_relaxed)) {
atomic_store_explicit(&g_rem_side[i].val, next, memory_order_release);
tiny_remote_sentinel_set(node);
return;
} else if (expect == k) {
// ← Duplicate detection
if (__builtin_expect(g_debug_remote_guard, 0)) {
uintptr_t observed = atomic_load_explicit((_Atomic uintptr_t*)node,
memory_order_relaxed);
tiny_remote_report_corruption("dup_push", node, observed);
uintptr_t aux = tiny_remote_pack_diag(0xA212u, base, ss_size, (uintptr_t)node);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, node, aux);
// ...dump + raise...
}
return; // ← Prevent duplicate
}
}
```
✓ Side table の CAS-or-collision チェック
✓ A212 コードdup_pushで検出・記録
✓ 既に key=k の entry があれば即座に return二重登録防止
**Box 2 評価: ✅ PASS - 二重 push は 3 層で防止。A214/A212 コード検出も有効。**
---
## Box 4: Publish/Fetch は通知のみ検証
### 不変条件
**publish/fetch 側から drain や owner_tid を触らない**
### 検証結果
#### ✅ 問題なし: tiny_publish_notify() は通知のみ
```c
// core/tiny_publish.c:13-34
void tiny_publish_notify(int class_idx, SuperSlab* ss, int slab_idx) {
if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) {
tiny_debug_ring_record(TINY_RING_EVENT_SUPERSLAB_ADOPT_FAIL,
(uint16_t)0xEEu, ss, (uintptr_t)class_idx);
return;
}
g_pub_notify_calls[class_idx]++;
tiny_debug_ring_record(TINY_RING_EVENT_SUPERSLAB_PUBLISH,
(uint16_t)class_idx, ss, (uintptr_t)slab_idx);
// ...トレース(副作用なし)...
tiny_mailbox_publish(class_idx, ss, slab_idx); // ← 単なる通知
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ mailbox へ (class_idx, ss, slab_idx) の 3-tuple を記録するのみ
#### ✅ 問題なし: tiny_mailbox_publish() は記録のみ
```c
// core/tiny_mailbox.c:109-119
void tiny_mailbox_publish(int class_idx, SuperSlab* ss, int slab_idx) {
tiny_mailbox_register(class_idx);
// Encode entry locally
uintptr_t ent = ((uintptr_t)ss) | ((uintptr_t)slab_idx & 0x3Fu);
uint32_t slot = g_tls_mailbox_slot[class_idx];
tiny_debug_ring_record(TINY_RING_EVENT_MAILBOX_PUBLISH, ...);
atomic_store_explicit(&g_pub_mailbox_entries[class_idx][slot], ent,
memory_order_release); // ← 単なる記録
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ メモリへの記録のみ
#### ✅ 問題なし: tiny_mailbox_fetch() は読み込みと提示のみ
```c
// core/tiny_mailbox.c:130-252
uintptr_t tiny_mailbox_fetch(int class_idx) {
// ...スロット走査...
uintptr_t ent = atomic_exchange_explicit(mailbox, (uintptr_t)0, memory_order_acq_rel);
if (ent) {
g_pub_mail_hits[class_idx]++;
SuperSlab* ss = (SuperSlab*)(ent & ~((uintptr_t)SUPERSLAB_SIZE_MIN - 1u));
int slab = (int)(ent & 0x3Fu);
tiny_debug_ring_record(TINY_RING_EVENT_MAILBOX_FETCH, ...);
return ent; // ← ヒントを返すのみ
}
return (uintptr_t)0;
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし
✓ fetch は単なる "ヒント提供"(候補の推薦)
#### ✅ 問題なし: ss_partial_publish() は owner リセット + unbind + 通知
```c
// core/hakmem_tiny.c:639-717
void ss_partial_publish(int class_idx, SuperSlab* ss) {
if (!ss) return;
// ① owner_tid リセットpublish の一部)
unsigned prev = atomic_exchange_explicit(&ss->listed, 1u, memory_order_acq_rel);
if (prev != 0u) return; // already listed
// ② 所有者をリセットadopt 準備)
int cap_pub = ss_slabs_capacity(ss);
for (int s = 0; s < cap_pub; s++) {
uint32_t prev = __atomic_exchange_n(&ss->slabs[s].owner_tid, 0u, __ATOMIC_RELEASE);
// ...記録のみ...
}
// ③ TLS unbindpublish 側が使わなくするため)
extern __thread TinyTLSSlab g_tls_slabs[];
if (g_tls_slabs[class_idx].ss == ss) {
g_tls_slabs[class_idx].ss = NULL;
g_tls_slabs[class_idx].meta = NULL;
g_tls_slabs[class_idx].slab_base = NULL;
g_tls_slabs[class_idx].slab_idx = 0;
}
// ④ hint 計算(提示用)
// ...hint を計算して ss->publish_hint セット...
// ⑤ ring に登録(通知)
for (int i = 0; i < SS_PARTIAL_RING; i++) {
// ...ring の empty slot を探して登録...
}
}
```
✓ drain 呼び出しなし(重要!)
✓ owner_tid リセットは「publish の責務」の範囲内adopter 準備)
**NOTE: publish 側から drain を呼ばない** ← Box 4 厳守
✓ 以下のコメント参照:
```c
// NOTE: Do NOT drain here! The old SuperSlab may have slabs owned by other threads
// that just adopted from it. Draining without ownership checks causes freelist corruption.
// The adopter will drain when needed (with proper ownership checks in tiny_refill.h).
```
#### ✅ 問題なし: ss_partial_adopt() は fetch + リセット+利用のみ
```c
// core/hakmem_tiny.c:719-742
SuperSlab* ss_partial_adopt(int class_idx) {
for (int i = 0; i < SS_PARTIAL_RING; i++) {
SuperSlab* ss = atomic_exchange_explicit(&g_ss_partial_ring[class_idx][i],
NULL, memory_order_acq_rel);
if (ss) {
// Clear listed flag to allow future publish
atomic_store_explicit(&ss->listed, 0u, memory_order_release);
g_ss_adopt_dbg[class_idx]++;
return ss; // ← 利用側へ返却
}
}
// Fallback: adopt from overflow stack
while (1) {
SuperSlab* head = atomic_load_explicit(&g_ss_partial_over[class_idx],
memory_order_acquire);
if (!head) break;
SuperSlab* next = head->partial_next;
if (atomic_compare_exchange_weak_explicit(&g_ss_partial_over[class_idx], &head, next,
memory_order_acq_rel, memory_order_relaxed)) {
atomic_store_explicit(&head->listed, 0u, memory_order_release);
g_ss_adopt_dbg[class_idx]++;
return head; // ← 利用側へ返却
}
}
return NULL;
}
```
✓ drain 呼び出しなし
✓ owner_tid 操作なし(すでに publish で 0 にされている)
✓ 単なる slab の検索+返却
#### ✅ 問題なし: adopt 側での drain は refill 境界で実施
```c
// core/hakmem_tiny_free.inc:696-740
// (superslab_refill 内)
SuperSlab* adopt = ss_partial_adopt(class_idx);
if (adopt && adopt->magic == SUPERSLAB_MAGIC) {
// ...best slab 探索...
if (best >= 0) {
uint32_t self = tiny_self_u32();
SlabHandle h = slab_try_acquire(adopt, best, self); // ← Box 3: 所有権取得
if (slab_is_valid(&h)) {
slab_drain_remote_full(&h); // ← Box 2: 所有権ガード下で drain
if (slab_remote_pending(&h)) {
// ...pending check...
slab_release(&h);
}
if (slab_freelist(&h)) {
tiny_tls_bind_slab(tls, h.ss, h.slab_idx); // ← Box 3: bind
return h.ss;
}
slab_release(&h);
}
}
}
```
**drain は採用側の refill 境界で実施** ← Box 4 完全遵守
✓ 所有権取得 → drain → bind の順序が正確
✓ slab_handle.h の slab_drain_remote() でガード
**Box 4 評価: ✅ PASS - publish/fetch は純粋な通知。drain は adopter 側境界でのみ実施。**
---
## 残り問題の検証: TOCTOU バグ(既知)
### 既知: Box 2→3 の TOCTOU バグ(修正済み)
前述の "drain 後に remote_pending チェック漏れ" は以下で修正済み:
```c
// core/hakmem_tiny_free.inc:714-717
SlabHandle h = slab_try_acquire(adopt, best, self);
if (slab_is_valid(&h)) {
slab_drain_remote_full(&h);
if (slab_remote_pending(&h)) { // ← チェック追加(修正)
slab_release(&h);
// continue to next candidate
}
}
```
✓ drain 完了後に remote_pending をチェック
✓ pending がまだあれば acquire を release して次へ
✓ TOCTOU window を最小化
---
## 追加調査: Remote A213/A202 コードの生成源特定
### A213: pre_push corruptionTLS guard scribble
```c
// core/hakmem_tiny_free.inc:1187-1207
if (__builtin_expect(head_word == TINY_REMOTE_SENTINEL && !dup_remote && g_debug_remote_guard, 0)) {
tiny_remote_watch_note("dup_scan_miss", ss, slab_idx, ptr, 0xA215u, my_tid, 0);
}
if (dup_remote) {
// ...A214...
}
if (__builtin_expect(g_remote_side_enable && (head_word & 0xFFFFu) == 0x6261u, 0)) {
// TLS guard scribble detected on the node's first word
uintptr_t aux = tiny_remote_pack_diag(0xA213u, ss_base, ss_size, (uintptr_t)ptr);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, ptr, aux);
tiny_remote_watch_mark(ptr, "pre_push", my_tid);
tiny_remote_watch_note("pre_push", ss, slab_idx, ptr, 0xA231u, my_tid, 0);
tiny_remote_report_corruption("pre_push", ptr, head_word);
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
return;
}
```
✓ A213: 発見元は hakmem_tiny_free.inc:1198-1206
✓ 原因: node の first word に 0x6261 (ba) scribble が見られた
✓ 意味: 同じ pointer で既に ss_remote_side_set が呼ばれている可能性
✓ 修正: dup_remote チェックで事前に防止(現実装で動作)
### A202: sentinel corruptiondrain 時)
```c
// core/hakmem_tiny_superslab.h:409-427
if (__builtin_expect(g_remote_side_enable, 0)) {
if (!tiny_remote_sentinel_ok(node)) {
uintptr_t aux = tiny_remote_pack_diag(0xA202u, base, ss_size, (uintptr_t)node);
tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID,
(uint16_t)ss->size_class, node, aux);
// ...corruption report...
if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; }
}
tiny_remote_side_clear(ss, slab_idx, node);
}
```
✓ A202: 発見元は hakmem_tiny_superslab.h:410
✓ 原因: drain 時に node の sentinel が不正0xBADA55... ではない)
✓ 意味: node の first word が何らかの理由で上書きされた
✓ 対策: g_remote_side_enable でも sentinel チェック
---
## Box Theory の完全性評価
### Box 境界チェックリスト
| Box | 機能 | 不変条件 | 検証 | 評価 |
|-----|------|---------|------|------|
| **Box 1** | Atomic Ops | CAS/Exchange の秩序付けRelease/Acquire | 記載省略(下層) | ✅ |
| **Box 2** | Remote Queue | push は freelist/owner に触れない | 二重 push: A214/A212 | ✅ PASS |
| **Box 3** | Ownership | acquire/release の正確性 | owner_tid CAS | ✅ PASS |
| **Box 4** | Publish/Adopt | publish から drain 呼ばない | 採用境界分離確認 | ✅ PASS |
| **Box 3↔2** | Drain boundary | ownership 確保後 drain | slab_handle.h 経由 | ✅ PASS |
| **Box 4→3** | Adopt boundary | drain→bind→owner の順序 | refill 1箇所 | ✅ PASS |
### 結論
**✅ Box 境界の不変条件は厳密に守られている。**
1. **Box 3 (Ownership)**:
- freelist push は owner_tid==my_tid のみ
- publish 時の owner リセットが明確
- slab_handle.h の SlabHandle でガード完全
2. **Box 2 (Remote Queue)**:
- 二重 push は 3 層で防止free 側: A214, side-set: A212, traverse limit: fail-safe
- remote_side の sentinel で追加保護
- drain 時の sentinel チェックで corruption 検出
3. **Box 4 (Publish/Fetch)**:
- publish は owner リセット+通知のみ
- drain は publish 側では呼ばない
- 採用側 refill 境界でのみ drainownership ガード下)
4. **remote_invalid の A213/A202 検出**:
- A213: dup_remote チェック1183で事前防止
- A202: sentinel 検査410で drain 時検出
- どちらも fail-fast で即座に報告・停止
---
## 推奨事項
### 現在の状態
**Box Theory の実装は健全です。散発的な remote_invalid は以下に起因する可能性:**
1. **Timing window**
- publish → unlistedcatalog から外れる)→ adopt の間に
- owner=0 のまま別スレッドが allocate する可能性は低いが、エッジケースあり得る
2. **Platform memory ordering**
- x86: Acquire/Release は効くが、他の platform では要注意
- memory_order_acq_rel で CAS してるので current は安全
3. **Rare race in ss_partial_adopt()**
- overflow stack での LIFO pop と新規登録の タイミング
- 概率は低いが、同時並行で複数スレッドが overflow を走査
### テスト・デバッグ提案
```bash
# 散発的なバグを局所化
HAKMEM_TINY_REMOTE_SIDE=1 # Side table 有効化
HAKMEM_DEBUG_COUNTERS=1 # 統計カウント
HAKMEM_TINY_RF_TRACE=1 # publish/fetch の トレース
HAKMEM_TINY_SS_ADOPT=1 # SuperSlab adopt 有効化
# 検出時のダンプ
HAKMEM_TINY_MAILBOX_SLOWDISC=1 # Slow discovery
```
---
## まとめ
**徹底検証の結果、Box 3, 2, 4 の不変条件は守られている。**
- Box 3: freelist push は所有権ガード完全 ✅
- Box 2: 二重 push は 3 層で防止 ✅
- Box 4: publish/fetch は純粋な通知、drain は adopter 側 ✅
remote_invalid (A213/A202) の散発は、Box Theory のバグではなく、
**edge case in timing** である可能性が高い。
TOCTOU window 最小化と memory barrier の強化で、
さらに robust化できる可能性あり。